difftreelog
refactor better static analysis error display
in: master
67 files changed
crates/jrsonnet-evaluator/src/analyze.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/analyze.rs
+++ b/crates/jrsonnet-evaluator/src/analyze.rs
@@ -472,7 +472,7 @@
match bind {
BindSpec::Field { into, .. } => self.alloc_destruct(into),
BindSpec::Function { name, .. } => {
- let (_, id) = self.define_local(name.clone(), None)?;
+ let (_, id) = self.define_local(name.value.clone(), Some(name.span.clone()))?;
Some(LDestruct::Full(id))
}
}
@@ -1337,18 +1337,18 @@
stack: &mut AnalysisStack,
taint: &mut AnalysisResult,
) -> LExpr {
- if let Expr::Function(params, body) = expr {
- return analyze_function(Some(name), params, body, stack, taint);
+ if let Expr::Function(span, params, body) = expr {
+ return analyze_function(Some(name), &span, ¶ms, body, stack, taint);
}
analyze(expr, stack, taint)
}
#[allow(clippy::too_many_lines)]
pub fn analyze(expr: &Expr, stack: &mut AnalysisStack, taint: &mut AnalysisResult) -> LExpr {
match expr {
- Expr::Literal(l) => match l {
+ Expr::Literal(span, l) => match l {
LiteralType::This => stack.use_this(taint).map_or_else(
|| {
- stack.report_error("`self` used outside of object", None);
+ stack.report_error("`self` used outside of object", Some(span.clone()));
LExpr::BadLocal("self")
},
LExpr::Slot,
@@ -1357,13 +1357,13 @@
if stack.use_super(taint).is_some() {
LExpr::Super
} else {
- stack.report_error("`super` used outside of object", None);
+ stack.report_error("`super` used outside of object", Some(span.clone()));
LExpr::BadLocal("super")
}
}
LiteralType::Dollar => stack.use_dollar(taint).map_or_else(
|| {
- stack.report_error("`$` used outside of object", None);
+ stack.report_error("`$` used outside of object", Some(span.clone()));
LExpr::BadLocal("$")
},
LExpr::Slot,
@@ -1475,7 +1475,9 @@
parts: parts_l,
}
}
- Expr::Function(params, body) => analyze_function(None, params, body, stack, taint),
+ Expr::Function(span, params, body) => {
+ analyze_function(None, span, params, body, stack, taint)
+ }
Expr::IfElse(ifelse) => {
let IfElse {
cond,
@@ -1541,15 +1543,22 @@
) -> LExpr {
match bind {
BindSpec::Field {
- value: Expr::Function(params, value),
+ value: Expr::Function(span, params, value),
into: Destruct::Full(name),
- } => analyze_function(Some(name.value.clone()), params, value, stack, taint),
+ } => analyze_function(Some(name.value.clone()), &span, params, value, stack, taint),
BindSpec::Field { value, .. } => analyze(value, stack, taint),
BindSpec::Function {
params,
value,
name,
- } => analyze_function(Some(name.clone()), params, value, stack, taint),
+ } => analyze_function(
+ Some(name.value.clone()),
+ &name.span,
+ params,
+ value,
+ stack,
+ taint,
+ ),
}
}
@@ -1595,6 +1604,7 @@
fn analyze_function(
name: Option<IStr>,
+ span: &Span,
params: &ExprParams,
body: &Expr,
stack: &mut AnalysisStack,
@@ -1648,15 +1658,15 @@
// function(x) x is an identity function
if l_params.len() == 1 && l_params[0].default.is_none() {
- stack.report_warning(
- "do not define identity functions manually, use std.id instead",
- None,
- );
#[allow(irrefutable_let_patterns, reason = "refutable with exp-destruct")]
if let LDestruct::Full(param_slot) = &l_params[0].destruct
&& let LExpr::Slot(LSlot::Local(s)) = &body_expr
&& s == param_slot
{
+ stack.report_warning(
+ "do not define identity functions manually, use std.id instead",
+ Some(span.clone()),
+ );
return LExpr::IdentityFunction {};
}
}
@@ -1727,7 +1737,14 @@
for (f, name) in fields.iter().zip(field_names) {
let value = stack.in_using_closure(|stack| {
if let Some(params) = &f.params {
- analyze_function(name.function_name(), params, &f.value, stack, taint)
+ analyze_function(
+ name.function_name(),
+ &f.name.span,
+ params,
+ &f.value,
+ stack,
+ taint,
+ )
} else {
analyze(&f.value, stack, taint)
}
@@ -1771,7 +1788,14 @@
process_local_frame(&comp.locals, stack, taint, |stack, taint| {
let value = stack.in_using_closure(|stack| {
if let Some(params) = &comp.field.params {
- analyze_function(None, params, &comp.field.value, stack, taint)
+ analyze_function(
+ None,
+ &comp.field.name.span,
+ params,
+ &comp.field.value,
+ stack,
+ taint,
+ )
} else {
analyze(&comp.field.value, stack, taint)
}
crates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth1#[cfg(feature = "explaining-traces")]2use std::cell::RefCell;3use std::{4 any::Any,5 fmt,6 path::{Component, Path, PathBuf},7};89use jrsonnet_gcmodule::Trace;10use jrsonnet_ir::CodeLocation;11#[cfg(feature = "explaining-traces")]12use jrsonnet_ir::Span;1314use crate::{Error, ResolvePathOwned, error::ErrorKind};1516/// The way paths should be displayed17#[derive(Clone, Trace)]18pub enum PathResolver {19 /// Only filename20 FileName,21 /// Absolute path22 Absolute,23 /// Path relative to base directory24 Relative(PathBuf),25}2627impl PathResolver {28 /// Will return `Self::Relative(cwd)`, or `Self::Absolute` on cwd failure29 pub fn new_cwd_fallback() -> Self {30 std::env::current_dir().map_or(Self::Absolute, Self::Relative)31 }32 pub fn resolve(&self, from: &Path) -> String {33 match self {34 Self::FileName => from35 .file_name()36 .expect("file name exists")37 .to_string_lossy()38 .into_owned(),39 Self::Absolute => from.to_string_lossy().into_owned(),40 Self::Relative(base) => {41 if from.is_relative() {42 return from.to_string_lossy().into_owned();43 }44 let diff = pathdiff::diff_paths(from, base).expect("base is absolute");45 let parents = diff46 .components()47 .take_while(|c| matches!(c, Component::ParentDir))48 .count();49 let base_depth = base50 .components()51 .filter(|c| matches!(c, Component::Normal(_)))52 .count();53 if parents > 0 && parents >= base_depth {54 return from.to_string_lossy().into_owned();55 }56 diff.to_string_lossy().into_owned()57 }58 }59 }60}6162/// Implements pretty-printing of traces63#[allow(clippy::module_name_repetitions)]64pub trait TraceFormat: Trace {65 fn write_trace(&self, out: &mut dyn fmt::Write, error: &Error) -> Result<(), fmt::Error>;66 fn format(&self, error: &Error) -> Result<String, fmt::Error> {67 let mut out = String::new();68 self.write_trace(&mut out, error)?;69 Ok(out)70 }71 fn as_any(&self) -> &dyn Any;72 fn as_any_mut(&mut self) -> &mut dyn Any;73}7475fn print_code_location(76 out: &mut impl fmt::Write,77 start: &CodeLocation,78 end: &CodeLocation,79) -> Result<(), fmt::Error> {80 if start.line == end.line {81 if start.column == end.column {82 write!(out, "{}:{}", start.line, start.column)?;83 } else {84 write!(85 out,86 "{}:{}-{}",87 start.line,88 start.column,89 end.column.saturating_sub(1)90 )?;91 }92 } else {93 write!(94 out,95 "{}:{}-{}:{}",96 start.line,97 start.column,98 end.line,99 end.column.saturating_sub(1)100 )?;101 }102 Ok(())103}104105/// vanilla-like jsonnet formatting106#[derive(Trace)]107pub struct CompactFormat {108 pub resolver: PathResolver,109 pub max_trace: usize,110 pub padding: usize,111}112impl Default for CompactFormat {113 fn default() -> Self {114 Self {115 resolver: PathResolver::Absolute,116 max_trace: 20,117 padding: 4,118 }119 }120}121122impl TraceFormat for CompactFormat {123 fn write_trace(&self, out: &mut dyn fmt::Write, error: &Error) -> Result<(), fmt::Error> {124 if let ErrorKind::ImportFileNotFound(from, import) = error.error() {125 let from = from126 .path()127 .map_or_else(|| from.to_string(), |path| self.resolver.resolve(path));128 let import = match import {129 ResolvePathOwned::Str(s) => s.clone(),130 ResolvePathOwned::Path(path_buf) => self.resolver.resolve(path_buf),131 };132 write!(out, "import file not found {import} from {from}")?;133 } else {134 write!(out, "{}", error.error())?;135 }136137 if let ErrorKind::ImportSyntaxError { path, error } = error.error() {138 use std::fmt::Write;139140 writeln!(out)?;141 let mut n = path.source_path().path().map_or_else(142 || path.source_path().to_string(),143 |r| self.resolver.resolve(r),144 );145 let offset = (error.location.1 as usize).min(path.code().len());146 #[expect(clippy::cast_possible_truncation, reason = "code is limited by 4gb")]147 let location = path148 .map_source_locations(&[offset as u32])149 .into_iter()150 .next()151 .unwrap();152153 write!(n, ":").unwrap();154 print_code_location(&mut n, &location, &location).unwrap();155 write!(out, "{:<p$}{n}", "", p = self.padding)?;156 }157 let file_names = error158 .trace()159 .0160 .iter()161 .map(|el| &el.location)162 .map(|location| {163 use std::fmt::Write;164 #[allow(clippy::option_if_let_else)]165 if let Some(location) = location {166 let mut resolved_path = match location.0.source_path().path() {167 Some(r) => self.resolver.resolve(r),168 None => location.0.source_path().to_string(),169 };170 // TODO: Process all trace elements first171 let location = location.0.map_source_locations(&[location.1, location.2]);172 write!(resolved_path, ":").unwrap();173 print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();174 write!(resolved_path, ":").unwrap();175 Some(resolved_path)176 } else {177 None178 }179 })180 .collect::<Vec<_>>();181 let align = file_names182 .iter()183 .flatten()184 .map(String::len)185 .max()186 .unwrap_or(0);187 for (el, file) in error.trace().0.iter().zip(file_names) {188 writeln!(out)?;189 if let Some(file) = file {190 write!(191 out,192 "{:<p$}{:<w$} {}",193 "",194 file,195 el.desc,196 p = self.padding,197 w = align198 )?;199 } else {200 write!(out, "{:<p$}{}", "", el.desc, p = self.padding)?;201 }202 }203 Ok(())204 }205206 fn as_any(&self) -> &dyn Any {207 self208 }209210 fn as_any_mut(&mut self) -> &mut dyn Any {211 self212 }213}214215#[derive(Trace)]216pub struct JsFormat {217 pub max_trace: usize,218}219impl TraceFormat for JsFormat {220 fn write_trace(&self, out: &mut dyn fmt::Write, error: &Error) -> Result<(), fmt::Error> {221 write!(out, "{}", error.error())?;222 for item in &error.trace().0 {223 writeln!(out)?;224 let desc = &item.desc;225 if let Some(source) = &item.location {226 let start_end = source.0.map_source_locations(&[source.1, source.2]);227 let resolved_path = source.0.source_path().path().map_or_else(228 || source.0.source_path().to_string(),229 |r| r.display().to_string(),230 );231232 write!(233 out,234 " at {} ({}:{}:{})",235 desc, resolved_path, start_end[0].line, start_end[0].column,236 )?;237 } else {238 write!(out, " during {desc}")?;239 }240 }241 Ok(())242 }243244 fn as_any(&self) -> &dyn Any {245 self246 }247248 fn as_any_mut(&mut self) -> &mut dyn Any {249 self250 }251}252253#[cfg(feature = "explaining-traces")]254#[derive(Trace)]255pub struct HiDocFormat {256 pub resolver: PathResolver,257 pub max_trace: usize,258}259#[cfg(feature = "explaining-traces")]260impl TraceFormat for HiDocFormat {261 #[allow(clippy::too_many_lines)]262 fn write_trace(&self, out: &mut dyn fmt::Write, error: &Error) -> Result<(), fmt::Error> {263 struct ResetData {264 loc: Span,265 }266 use hi_doc::{Formatting, SnippetBuilder, Text, source_to_ansi};267268 write!(out, "{}", error.error())?;269 if let ErrorKind::ImportSyntaxError { path, error } = error.error() {270 writeln!(out)?;271 let mut builder = SnippetBuilder::new(path.code());272 builder273 .error(Text::fragment("syntax error", Formatting::default()))274 .range(error.location.range())275 .build();276 let source = builder.build();277 let ansi = source_to_ansi(&source);278 write!(out, "{ansi}")?;279 }280 if let ErrorKind::StaticAnalysisError(diagnostics) = error.error() {281 use crate::analyze::DiagLevel;282 let mut builder: Option<SnippetBuilder> = None;283 let mut current_src: Option<&str> = None;284 let flush = |builder: Option<SnippetBuilder>,285 out: &mut dyn fmt::Write|286 -> Result<(), fmt::Error> {287 if let Some(b) = builder {288 let ansi = source_to_ansi(&b.build());289 write!(out, "\n{}", ansi.trim_end())?;290 }291 Ok(())292 };293 for diag in diagnostics {294 if let Some(span) = &diag.span {295 let src = span.0.code();296 if current_src != Some(src) {297 flush(builder.take(), out)?;298 builder = Some(SnippetBuilder::new(src));299 current_src = Some(src);300 }301 let b = builder.as_mut().unwrap();302 let ab = match diag.level {303 DiagLevel::Error => {304 b.error(Text::fragment(diag.message.clone(), Formatting::default()))305 }306 DiagLevel::Warning => {307 b.warning(Text::fragment(diag.message.clone(), Formatting::default()))308 }309 };310 ab.range(span.range()).build();311 } else {312 flush(builder.take(), out)?;313 current_src = None;314 let prefix = match diag.level {315 DiagLevel::Error => "error",316 DiagLevel::Warning => "warning",317 };318 write!(out, "\n{prefix}: {}", diag.message)?;319 }320 }321 flush(builder, out)?;322 }323 let trace = &error.trace();324 let snippet_builder: RefCell<Option<SnippetBuilder>> = RefCell::new(None);325 let mut last_location: Option<Span> = None;326 let mut flush_builder = |data: Option<ResetData>| {327 use std::fmt::Write;328 let mut out = String::new();329 let location_changed = if let Some(ResetData { loc }) = &data {330 if last_location.as_ref().map(|l| l.0.code()) != Some(loc.0.code()) {331 true332 } else if let (Some(last), new) = (&last_location, loc) {333 // Reverse condition if traceback334 last.1 > new.1 || last.2 > new.2335 } else {336 false337 }338 } else {339 true340 };341 if location_changed {342 if let Some(builder) = snippet_builder.borrow_mut().take() {343 let rendered = builder.build();344 let ansi = source_to_ansi(&rendered);345 if let Some(loc) = &last_location {346 let _ = writeln!(out, "...at {}", loc.0.source_path());347 }348 let _ = write!(out, "{}", ansi.trim_end());349 }350 last_location = None;351352 if let Some(ResetData { loc }) = data {353 *snippet_builder.borrow_mut() = Some(SnippetBuilder::new(loc.0.code()));354 last_location = Some(loc);355 }356 }357 if out.is_empty() {358 return None;359 }360 Some(out)361 };362 for item in &trace.0 {363 let desc = &item.desc;364 if let Some(source) = &item.location {365 if let Some(flushed) = flush_builder(Some(ResetData {366 loc: source.clone(),367 })) {368 writeln!(out)?;369 write!(out, "{flushed}")?;370 }371 let mut builder = snippet_builder.borrow_mut();372 let builder = builder.as_mut().unwrap();373 builder374 .note(Text::fragment(desc, Formatting::default()))375 .range(source.1 as usize..=(source.2 as usize - 1).max(source.1 as usize))376 .build();377 } else {378 if let Some(flushed) = flush_builder(None) {379 writeln!(out)?;380 write!(out, "{flushed}")?;381 }382 writeln!(out)?;383 write!(out, " {desc}")?;384 }385 }386387 if let Some(flushed) = flush_builder(None) {388 writeln!(out)?;389 write!(out, "{flushed}")?;390 }391 Ok(())392 }393394 fn as_any(&self) -> &dyn Any {395 self396 }397398 fn as_any_mut(&mut self) -> &mut dyn Any {399 self400 }401}1#[cfg(feature = "explaining-traces")]2use std::cell::RefCell;3use std::{4 any::Any,5 fmt,6 path::{Component, Path, PathBuf},7};89use jrsonnet_gcmodule::Trace;10use jrsonnet_ir::{CodeLocation, Span};1112use crate::{Error, ResolvePathOwned, analyze::DiagLevel, error::ErrorKind};1314/// The way paths should be displayed15#[derive(Clone, Trace)]16pub enum PathResolver {17 /// Only filename18 FileName,19 /// Absolute path20 Absolute,21 /// Path relative to base directory22 Relative(PathBuf),23}2425impl PathResolver {26 /// Will return `Self::Relative(cwd)`, or `Self::Absolute` on cwd failure27 pub fn new_cwd_fallback() -> Self {28 std::env::current_dir().map_or(Self::Absolute, Self::Relative)29 }30 pub fn resolve(&self, from: &Path) -> String {31 match self {32 Self::FileName => from33 .file_name()34 .expect("file name exists")35 .to_string_lossy()36 .into_owned(),37 Self::Absolute => from.to_string_lossy().into_owned(),38 Self::Relative(base) => {39 if from.is_relative() {40 return from.to_string_lossy().into_owned();41 }42 let diff = pathdiff::diff_paths(from, base).expect("base is absolute");43 let parents = diff44 .components()45 .take_while(|c| matches!(c, Component::ParentDir))46 .count();47 let base_depth = base48 .components()49 .filter(|c| matches!(c, Component::Normal(_)))50 .count();51 if parents > 0 && parents >= base_depth {52 return from.to_string_lossy().into_owned();53 }54 diff.to_string_lossy().into_owned()55 }56 }57 }58}5960/// Implements pretty-printing of traces61#[allow(clippy::module_name_repetitions)]62pub trait TraceFormat: Trace {63 fn write_trace(&self, out: &mut dyn fmt::Write, error: &Error) -> Result<(), fmt::Error>;64 fn format(&self, error: &Error) -> Result<String, fmt::Error> {65 let mut out = String::new();66 self.write_trace(&mut out, error)?;67 Ok(out)68 }69 fn as_any(&self) -> &dyn Any;70 fn as_any_mut(&mut self) -> &mut dyn Any;71}7273fn span_label(resolver: &PathResolver, span: &Span) -> String {74 use std::fmt::Write;75 let mut path = span76 .077 .source_path()78 .path()79 .map_or_else(|| span.0.source_path().to_string(), |p| resolver.resolve(p));80 #[expect(clippy::cast_possible_truncation, reason = "code is limited by 4gb")]81 let len = span.0.code().len() as u32;82 let start = span.1.min(len);83 let end = span.2.min(len);84 let (start_loc, end_loc) = if start == end {85 let [loc] = span.0.map_source_locations(&[start]);86 (loc, loc)87 } else {88 let [s, e] = span.0.map_source_locations(&[start, end]);89 (s, e)90 };91 write!(path, ":").unwrap();92 print_code_location(&mut path, &start_loc, &end_loc).unwrap();93 path94}9596#[cfg(feature = "explaining-traces")]97fn span_render_range(span: &Span) -> Option<std::ops::RangeInclusive<usize>> {98 let len = span.0.code().len();99 if len == 0 {100 return None;101 }102 let max = len - 1;103 let r = span.range();104 Some((*r.start()).min(max)..=(*r.end()).min(max))105}106107fn diag_level_label(level: DiagLevel) -> &'static str {108 match level {109 DiagLevel::Error => "error",110 DiagLevel::Warning => "warning",111 }112}113114fn print_code_location(115 out: &mut impl fmt::Write,116 start: &CodeLocation,117 end: &CodeLocation,118) -> Result<(), fmt::Error> {119 let end_col = end.column.saturating_sub(1).max(start.column);120 if start.line == end.line {121 if start.column == end_col {122 write!(out, "{}:{}", start.line, start.column)?;123 } else {124 write!(out, "{}:{}-{}", start.line, start.column, end_col)?;125 }126 } else {127 write!(128 out,129 "{}:{}-{}:{}",130 start.line, start.column, end.line, end_col131 )?;132 }133 Ok(())134}135136/// vanilla-like jsonnet formatting137#[derive(Trace)]138pub struct CompactFormat {139 pub resolver: PathResolver,140 pub max_trace: usize,141 pub padding: usize,142}143impl Default for CompactFormat {144 fn default() -> Self {145 Self {146 resolver: PathResolver::Absolute,147 max_trace: 20,148 padding: 4,149 }150 }151}152153impl TraceFormat for CompactFormat {154 fn write_trace(&self, out: &mut dyn fmt::Write, error: &Error) -> Result<(), fmt::Error> {155 match error.error() {156 ErrorKind::ImportFileNotFound(from, import) => {157 let from = from158 .path()159 .map_or_else(|| from.to_string(), |path| self.resolver.resolve(path));160 let import = match import {161 ResolvePathOwned::Str(s) => s.clone(),162 ResolvePathOwned::Path(path_buf) => self.resolver.resolve(path_buf),163 };164 write!(out, "import file not found {import} from {from}")?;165 }166 ErrorKind::StaticAnalysisError(_) => {167 write!(out, "static analysis errors")?;168 }169 _ => {170 write!(out, "{}", error.error())?;171 }172 }173174 if let ErrorKind::StaticAnalysisError(diagnostics) = error.error() {175 let labels: Vec<Option<String>> = diagnostics176 .iter()177 .map(|d| d.span.as_ref().map(|s| span_label(&self.resolver, s)))178 .collect();179 let align = labels.iter().flatten().map(String::len).max().unwrap_or(0);180 let cont_indent = " ".repeat(self.padding + align + 1);181 for (diag, label) in diagnostics.iter().zip(labels.iter()) {182 writeln!(out)?;183 let level = diag_level_label(diag.level);184 let message = diag.message.replace('\n', &format!("\n{cont_indent}"));185 let label = label.as_deref().unwrap_or("");186 write!(187 out,188 "{:<p$}{label:<w$} {level}: {message}",189 "",190 p = self.padding,191 w = align,192 )?;193 }194 }195196 if let ErrorKind::ImportSyntaxError { error, .. } = error.error() {197 writeln!(out)?;198 let label = span_label(&self.resolver, &error.location);199 write!(out, "{:<p$}{label}", "", p = self.padding)?;200 }201 let file_names = error202 .trace()203 .0204 .iter()205 .map(|el| {206 el.location.as_ref().map(|loc| {207 use std::fmt::Write;208 let mut s = span_label(&self.resolver, loc);209 write!(s, ":").unwrap();210 s211 })212 })213 .collect::<Vec<_>>();214 let align = file_names215 .iter()216 .flatten()217 .map(String::len)218 .max()219 .unwrap_or(0);220 for (el, file) in error.trace().0.iter().zip(file_names) {221 writeln!(out)?;222 if let Some(file) = file {223 write!(224 out,225 "{:<p$}{:<w$} {}",226 "",227 file,228 el.desc,229 p = self.padding,230 w = align231 )?;232 } else {233 write!(out, "{:<p$}{}", "", el.desc, p = self.padding)?;234 }235 }236 Ok(())237 }238239 fn as_any(&self) -> &dyn Any {240 self241 }242243 fn as_any_mut(&mut self) -> &mut dyn Any {244 self245 }246}247248#[derive(Trace)]249pub struct JsFormat {250 pub max_trace: usize,251}252impl TraceFormat for JsFormat {253 fn write_trace(&self, out: &mut dyn fmt::Write, error: &Error) -> Result<(), fmt::Error> {254 write!(out, "{}", error.error())?;255 for item in &error.trace().0 {256 writeln!(out)?;257 let desc = &item.desc;258 if let Some(source) = &item.location {259 let start_end = source.0.map_source_locations(&[source.1, source.2]);260 let resolved_path = source.0.source_path().path().map_or_else(261 || source.0.source_path().to_string(),262 |r| r.display().to_string(),263 );264265 write!(266 out,267 " at {} ({}:{}:{})",268 desc, resolved_path, start_end[0].line, start_end[0].column,269 )?;270 } else {271 write!(out, " during {desc}")?;272 }273 }274 Ok(())275 }276277 fn as_any(&self) -> &dyn Any {278 self279 }280281 fn as_any_mut(&mut self) -> &mut dyn Any {282 self283 }284}285286#[cfg(feature = "explaining-traces")]287#[derive(Trace)]288pub struct HiDocFormat {289 pub resolver: PathResolver,290 pub max_trace: usize,291}292#[cfg(feature = "explaining-traces")]293impl TraceFormat for HiDocFormat {294 #[allow(clippy::too_many_lines)]295 fn write_trace(&self, out: &mut dyn fmt::Write, error: &Error) -> Result<(), fmt::Error> {296 struct ResetData {297 loc: Span,298 }299 use hi_doc::{Formatting, SnippetBuilder, Text, source_to_ansi};300301 match error.error() {302 ErrorKind::StaticAnalysisError(_) => write!(out, "static analysis errors")?,303 _ => write!(out, "{}", error.error())?,304 }305 if let ErrorKind::ImportSyntaxError { path, error } = error.error() {306 writeln!(out, "\n...at {}", path.source_path())?;307 if let Some(range) = span_render_range(&error.location) {308 let mut builder = SnippetBuilder::new(path.code());309 builder310 .error(Text::fragment("syntax error", Formatting::default()))311 .range(range)312 .build();313 let ansi = source_to_ansi(&builder.build());314 write!(out, "{}", ansi.trim_end())?;315 }316 }317 if let ErrorKind::StaticAnalysisError(diagnostics) = error.error() {318 let mut builder: Option<(SnippetBuilder, Span)> = None;319 let flush = |slot: Option<(SnippetBuilder, Span)>,320 out: &mut dyn fmt::Write|321 -> Result<(), fmt::Error> {322 if let Some((b, anchor)) = slot {323 writeln!(out, "\n...at {}", anchor.0.source_path())?;324 let ansi = source_to_ansi(&b.build());325 write!(out, "{}", ansi.trim_end())?;326 }327 Ok(())328 };329 for diag in diagnostics {330 if let Some(span) = &diag.span {331 let Some(range) = span_render_range(span) else {332 continue;333 };334 let same_src = builder.as_ref().is_some_and(|(_, a)| a.0 == span.0);335 if !same_src {336 flush(builder.take(), out)?;337 builder = Some((SnippetBuilder::new(span.0.code()), span.clone()));338 }339 let b = &mut builder.as_mut().unwrap().0;340 let ab = match diag.level {341 DiagLevel::Error => {342 b.error(Text::fragment(diag.message.clone(), Formatting::default()))343 }344 DiagLevel::Warning => {345 b.warning(Text::fragment(diag.message.clone(), Formatting::default()))346 }347 };348 ab.range(range).build();349 } else {350 flush(builder.take(), out)?;351 let prefix = diag_level_label(diag.level);352 write!(out, "\n{prefix}: {}", diag.message)?;353 }354 }355 flush(builder, out)?;356 }357 let trace = &error.trace();358 let snippet_builder: RefCell<Option<SnippetBuilder>> = RefCell::new(None);359 let mut last_location: Option<Span> = None;360 let mut flush_builder = |data: Option<ResetData>| {361 use std::fmt::Write;362 let mut out = String::new();363 let location_changed = if let Some(ResetData { loc }) = &data {364 if last_location.as_ref().map(|l| l.0.code()) != Some(loc.0.code()) {365 true366 } else if let (Some(last), new) = (&last_location, loc) {367 // Reverse condition if traceback368 last.1 > new.1 || last.2 > new.2369 } else {370 false371 }372 } else {373 true374 };375 if location_changed {376 if let Some(builder) = snippet_builder.borrow_mut().take() {377 let rendered = builder.build();378 let ansi = source_to_ansi(&rendered);379 if let Some(loc) = &last_location {380 let _ = writeln!(out, "...at {}", loc.0.source_path());381 }382 let _ = write!(out, "{}", ansi.trim_end());383 }384 last_location = None;385386 if let Some(ResetData { loc }) = data {387 *snippet_builder.borrow_mut() = Some(SnippetBuilder::new(loc.0.code()));388 last_location = Some(loc);389 }390 }391 if out.is_empty() {392 return None;393 }394 Some(out)395 };396 for item in &trace.0 {397 let desc = &item.desc;398 if let Some(source) = &item.location {399 if let Some(flushed) = flush_builder(Some(ResetData {400 loc: source.clone(),401 })) {402 writeln!(out)?;403 write!(out, "{flushed}")?;404 }405 let mut builder = snippet_builder.borrow_mut();406 let builder = builder.as_mut().unwrap();407 builder408 .note(Text::fragment(desc, Formatting::default()))409 .range(source.1 as usize..=(source.2 as usize - 1).max(source.1 as usize))410 .build();411 } else {412 if let Some(flushed) = flush_builder(None) {413 writeln!(out)?;414 write!(out, "{flushed}")?;415 }416 writeln!(out)?;417 write!(out, " {desc}")?;418 }419 }420421 if let Some(flushed) = flush_builder(None) {422 writeln!(out)?;423 write!(out, "{flushed}")?;424 }425 Ok(())426 }427428 fn as_any(&self) -> &dyn Any {429 self430 }431432 fn as_any_mut(&mut self) -> &mut dyn Any {433 self434 }435}crates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir-parser/src/lib.rs
+++ b/crates/jrsonnet-ir-parser/src/lib.rs
@@ -77,6 +77,13 @@
self.offset += 1;
}
+ fn eat_any_spanned(&mut self) -> Span {
+ let start = self.span_start();
+ self.eat_any();
+ let end = self.span_end();
+ Span(self.source.clone(), start, end)
+ }
+
fn at_eof(&self) -> bool {
self.offset >= self.lexemes.len()
}
@@ -114,6 +121,12 @@
self.eat_any();
Ok(())
}
+ fn eat_spanned(&mut self, t: SyntaxKind) -> Result<Span> {
+ let start = self.span_start();
+ self.eat(t)?;
+ let end = self.span_end();
+ Ok(Span(self.source.clone(), start, end))
+ }
fn span_start(&self) -> u32 {
if self.at_eof() {
@@ -234,7 +247,7 @@
Ok(IStr::from(text))
}
-fn literal(p: &mut Parser<'_>) -> Option<LiteralType> {
+fn literal(p: &mut Parser<'_>) -> Option<(Span, LiteralType)> {
let t = match p.peek() {
T![self] => LiteralType::This,
T![super] => LiteralType::Super,
@@ -244,8 +257,7 @@
T![false] => LiteralType::False,
_ => return None,
};
- p.eat_any();
- Some(t)
+ Some((p.eat_any_spanned(), t))
}
fn assert_stmt(p: &mut Parser<'_>) -> Result<AssertStmt> {
@@ -482,20 +494,20 @@
});
}
}
- let name_spanned = spanned(p, ident)?;
+ let name = spanned(p, ident)?;
if p.try_eat(T!['(']) {
let ps = params(p)?;
p.eat(T![')'])?;
p.eat(T![=])?;
Ok(BindSpec::Function {
- name: name_spanned.value,
+ name,
params: ps,
value: expr(p)?,
})
} else {
p.eat(T![=])?;
Ok(BindSpec::Field {
- into: Destruct::Full(name_spanned),
+ into: Destruct::Full(name),
value: expr(p)?,
})
}
@@ -676,8 +688,8 @@
#[allow(clippy::too_many_lines)]
fn expr_basic(p: &mut Parser<'_>) -> Result<Expr> {
- if let Some(lit) = literal(p) {
- return Ok(Expr::Literal(lit));
+ if let Some((span, lit)) = literal(p) {
+ return Ok(Expr::Literal(span, lit));
}
match p.peek() {
@@ -755,12 +767,12 @@
T![if] => Ok(Expr::IfElse(Box::new(if_else(p)?))),
T![function] => {
- p.eat(T![function])?;
+ let span = p.eat_spanned(T![function])?;
p.eat(T!['('])?;
let ps = params(p)?;
p.eat(T![')'])?;
let body = expr(p)?;
- Ok(Expr::Function(ps, Box::new(body)))
+ Ok(Expr::Function(span, ps, Box::new(body)))
}
T![assert] => {
@@ -820,7 +832,7 @@
if parts.is_empty() {
return;
}
- let old = std::mem::replace(e, Expr::Literal(LiteralType::Null));
+ let old = std::mem::replace(e, Expr::Str(IStr::empty()));
*e = Expr::Index {
indexable: Box::new(old),
parts: std::mem::take(parts),
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__function_and_call.snap.newdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__function_and_call.snap.new
@@ -0,0 +1,81 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+assertion_line: 1110
+expression: "format!(\"{v:#?}\")"
+---
+LocalExpr(
+ [
+ Function {
+ name: "f" from virtual:<test>:6-7,
+ params: ExprParams {
+ exprs: [
+ ExprParam {
+ destruct: Full(
+ "x" from virtual:<test>:8-9,
+ ),
+ default: None,
+ },
+ ExprParam {
+ destruct: Full(
+ "y" from virtual:<test>:11-12,
+ ),
+ default: Some(
+ Num(
+ 1.0,
+ ),
+ ),
+ },
+ ],
+ signature: FunctionSignature(
+ [
+ ParamParse {
+ name: Named(
+ "x",
+ ),
+ default: None,
+ },
+ ParamParse {
+ name: Named(
+ "y",
+ ),
+ default: Exists,
+ },
+ ],
+ ),
+ binds_len: 2,
+ },
+ value: BinaryOp(
+ BinaryOp {
+ lhs: Var(
+ "x" from virtual:<test>:18-19,
+ ),
+ op: Add,
+ rhs: Var(
+ "y" from virtual:<test>:22-23,
+ ),
+ },
+ ),
+ },
+ ],
+ Apply(
+ Var(
+ "f" from virtual:<test>:25-26,
+ ),
+ ArgsDesc {
+ unnamed: [
+ Num(
+ 2.0,
+ ),
+ ],
+ names: [
+ "y",
+ ],
+ values: [
+ Num(
+ 3.0,
+ ),
+ ],
+ } from virtual:<test>:26-34,
+ false,
+ ),
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@default_nondefault.jsonnet.snap.newdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@default_nondefault.jsonnet.snap.new
@@ -0,0 +1,56 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+assertion_line: 1176
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/default_nondefault.jsonnet
+---
+LocalExpr(
+ [
+ Function {
+ name: "x" from virtual:<test>:6-7,
+ params: ExprParams {
+ exprs: [
+ ExprParam {
+ destruct: Full(
+ "foo" from virtual:<test>:8-11,
+ ),
+ default: Some(
+ Str(
+ "foo",
+ ),
+ ),
+ },
+ ExprParam {
+ destruct: Full(
+ "bar" from virtual:<test>:21-24,
+ ),
+ default: None,
+ },
+ ],
+ signature: FunctionSignature(
+ [
+ ParamParse {
+ name: Named(
+ "foo",
+ ),
+ default: Exists,
+ },
+ ParamParse {
+ name: Named(
+ "bar",
+ ),
+ default: None,
+ },
+ ],
+ ),
+ binds_len: 2,
+ },
+ value: Literal(
+ Null,
+ ),
+ },
+ ],
+ Literal(
+ Null,
+ ),
+)
crates/jrsonnet-ir/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir/src/expr.rs
+++ b/crates/jrsonnet-ir/src/expr.rs
@@ -288,7 +288,7 @@
value: Expr,
},
Function {
- name: IStr,
+ name: Spanned<IStr>,
params: ExprParams,
value: Expr,
},
@@ -404,7 +404,7 @@
/// Syntax base
#[derive(Debug, PartialEq, Acyclic)]
pub enum Expr {
- Literal(LiteralType),
+ Literal(Span, LiteralType),
/// String value: "hello"
Str(IStr),
@@ -454,7 +454,7 @@
parts: Vec<IndexPart>,
},
/// function(x) x
- Function(ExprParams, Box<Expr>),
+ Function(Span, ExprParams, Box<Expr>),
/// if true == false then 1 else 2
IfElse(Box<IfElse>),
Slice(Box<Slice>),
crates/jrsonnet-ir/src/visit.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir/src/visit.rs
+++ b/crates/jrsonnet-ir/src/visit.rs
@@ -178,7 +178,7 @@
}
pub fn visit_expr<V: Visitor>(v: &mut V, e: &Expr) {
match e {
- Expr::Literal(_literal_type) => {}
+ Expr::Literal(_span, _literal_type) => {}
Expr::Str(_istr) => {}
Expr::Num(_num) => {}
Expr::Var(_spanned) => {}
@@ -254,7 +254,7 @@
v.visit_expr(value);
}
}
- Expr::Function(expr_params, expr) => {
+ Expr::Function(_span, expr_params, expr) => {
visit_params(v, expr_params);
v.visit_expr(expr);
}
crates/jrsonnet-peg-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-peg-parser/src/lib.rs
+++ b/crates/jrsonnet-peg-parser/src/lib.rs
@@ -131,7 +131,7 @@
pub rule bind(s: &ParserSettings) -> BindSpec
= into:destruct(s) _ "=" _ value:expr(s) {BindSpec::Field{into, value}}
- / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {BindSpec::Function{name, params, value}}
+ / name:spanned(<id()>, s) _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {BindSpec::Function{name, params, value}}
pub rule assertion(s: &ParserSettings) -> AssertStmt
= keyword("assert") _ assertion:spanned(<expr(s)>, s) message:(_ ":" _ e:expr(s) {e})? { AssertStmt{assertion, message} }
@@ -290,14 +290,14 @@
}))}
pub rule literal(s: &ParserSettings) -> Expr
- = v:(
+ = a:position!() v:(
keyword("null") {LiteralType::Null}
/ keyword("true") {LiteralType::True}
/ keyword("false") {LiteralType::False}
/ keyword("self") {LiteralType::This}
/ keyword("$") {LiteralType::Dollar}
/ keyword("super") {LiteralType::Super}
- ) {Expr::Literal(v)}
+ ) b:position!() {Expr::Literal(Span(s.source.clone(), codeidx(a), codeidx(b)), v)}
rule import_kind() -> ImportKind
= keyword("importstr") { ImportKind::Str }
@@ -319,7 +319,7 @@
/ local_expr(s)
/ if_then_else_expr(s)
- / keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, Box::new(expr))}
+ / kw:spanned(<keyword("function")>, s) _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(kw.span, params, Box::new(expr))}
/ assert:assertion(s) _ ";" _ rest:expr(s) { Expr::AssertExpr(Box::new(AssertExpr{
assert, rest
})) }
tests/cpp_test_suite_golden_override/error.03.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.03.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.03.jsonnet.golden
@@ -1,3 +1,3 @@
runtime error: foo
error.03.jsonnet:17:21-25: error statement
- error.03.jsonnet:18:8-8: field <x> access
\ No newline at end of file
+ error.03.jsonnet:18:8: field <x> access
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.07.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.07.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.07.jsonnet.golden
@@ -1,4 +1,4 @@
runtime error: sarcasm
error.07.jsonnet:18:31-35: error statement
- error.07.jsonnet:17:33-33: element <3> access
+ error.07.jsonnet:17:33: element <3> access
error.07.jsonnet:18:20-53: function <third> call
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.args_commafodder.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.args_commafodder.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.args_commafodder.jsonnet.golden
@@ -1 +1,4 @@
-static analysis errors: undefined local: foo; undefined local: bar; undefined local: qux
\ No newline at end of file
+static analysis errors
+ error.args_commafodder.jsonnet:1:1-3 error: undefined local: foo
+ error.args_commafodder.jsonnet:2:3-5 error: undefined local: bar
+ error.args_commafodder.jsonnet:4:7-9 error: undefined local: qux
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.computed_field_scope.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.computed_field_scope.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.computed_field_scope.jsonnet.golden
@@ -1 +1,3 @@
-static analysis errors: undefined local: x; unused local: x
\ No newline at end of file
+static analysis errors
+ error.computed_field_scope.jsonnet:17:21 error: undefined local: x
+ error.computed_field_scope.jsonnet:17:9 warning: unused local: x
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.field_not_exist.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.field_not_exist.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.field_not_exist.jsonnet.golden
@@ -1,2 +1,2 @@
no such field: y
- error.field_not_exist.jsonnet:17:10-10: field <y> access
\ No newline at end of file
+ error.field_not_exist.jsonnet:17:10: field <y> access
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.function_duplicate_param.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.function_duplicate_param.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.function_duplicate_param.jsonnet.golden
@@ -1 +1,3 @@
-static analysis errors: local is already defined in the current frame: x; do not define identity functions manually, use std.id instead
\ No newline at end of file
+static analysis errors
+ error.function_duplicate_param.jsonnet:17:13 error: local is already defined in the current frame: x
+ error.function_duplicate_param.jsonnet:17:1-8 warning: do not define identity functions manually, use std.id instead
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.import_static-check-failure.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.import_static-check-failure.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.import_static-check-failure.jsonnet.golden
@@ -1,2 +1,3 @@
-static analysis errors: undefined local: x
+static analysis errors
+ lib/static_check_failure.jsonnet:2:1 error: undefined local: x
error.import_static-check-failure.jsonnet:1:1-6: import
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.import_syntax-error.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.import_syntax-error.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.import_syntax-error.jsonnet.golden
@@ -1,4 +1,4 @@
syntax error: unterminated double-quoted string
- lib/syntax_error.jsonnet:1:1
- lib/syntax_error.jsonnet:1:1-2:0: parse imported
+ lib/syntax_error.jsonnet:1:1-2:1
+ lib/syntax_error.jsonnet:1:1-2:1: parse imported
error.import_syntax-error.jsonnet:1:1-6: import
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.overflow.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.overflow.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.overflow.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: invalid number value: non-finite
- error.overflow.jsonnet:17:1
+ error.overflow.jsonnet:17:1-5
error.overflow.jsonnet:17:1-5: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.overflow3.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.overflow3.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.overflow3.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: invalid number value: non-finite
- error.overflow3.jsonnet:17:1
+ error.overflow3.jsonnet:17:1-5
error.overflow3.jsonnet:17:1-5: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.array_comma.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.array_comma.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.array_comma.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: expected ']', got number "3"
error.parse.array_comma.jsonnet:17:7
- error.parse.array_comma.jsonnet:17:7-7: parse imported
\ No newline at end of file
+ error.parse.array_comma.jsonnet:17:7: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.function_arg_positional_after_named.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.function_arg_positional_after_named.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.function_arg_positional_after_named.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: positional argument after named argument
error.parse.function_arg_positional_after_named.jsonnet:19:10
- error.parse.function_arg_positional_after_named.jsonnet:19:10-10: parse imported
\ No newline at end of file
+ error.parse.function_arg_positional_after_named.jsonnet:19:10: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.import_not_literal.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.import_not_literal.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.import_not_literal.jsonnet.golden
@@ -1 +1,2 @@
-static analysis errors: import path must be a string literal
\ No newline at end of file
+static analysis errors
+ error.parse.import_not_literal.jsonnet:17:1-6 error: import path must be a string literal
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.index_unterminated.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.index_unterminated.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.index_unterminated.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unexpected end of file
error.parse.index_unterminated.jsonnet:17:3
- error.parse.index_unterminated.jsonnet:17:3-0:0: parse imported
\ No newline at end of file
+ error.parse.index_unterminated.jsonnet:17:3: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.method_plus.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.method_plus.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.method_plus.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: expected ':', got '+'
error.parse.method_plus.jsonnet:17:18
- error.parse.method_plus.jsonnet:17:18-18: parse imported
\ No newline at end of file
+ error.parse.method_plus.jsonnet:17:18: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.object_comma.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.object_comma.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.object_comma.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: expected '}', got identifier "z"
error.parse.object_comma.jsonnet:17:11
- error.parse.object_comma.jsonnet:17:11-11: parse imported
\ No newline at end of file
+ error.parse.object_comma.jsonnet:17:11: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.object_comprehension_local_clash.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.object_comprehension_local_clash.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.object_comprehension_local_clash.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: expected '}', got ':'
error.parse.object_comprehension_local_clash.jsonnet:17:29
- error.parse.object_comprehension_local_clash.jsonnet:17:29-29: parse imported
\ No newline at end of file
+ error.parse.object_comprehension_local_clash.jsonnet:17:29: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.object_local_clash.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.object_local_clash.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.object_local_clash.jsonnet.golden
@@ -1 +1,3 @@
-static analysis errors: local is already defined in the current frame: x; unused local: x
\ No newline at end of file
+static analysis errors
+ error.parse.object_local_clash.jsonnet:17:21 error: local is already defined in the current frame: x
+ error.parse.object_local_clash.jsonnet:17:9 warning: unused local: x
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.self_in_computed_field.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.self_in_computed_field.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.self_in_computed_field.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: expected field name, got 'self'
- error.parse.self_in_computed_field.jsonnet:17:15
+ error.parse.self_in_computed_field.jsonnet:17:15-18
error.parse.self_in_computed_field.jsonnet:17:15-18: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.static_error_bad_number.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.static_error_bad_number.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.static_error_bad_number.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unexpected '.'
error.parse.static_error_bad_number.jsonnet:17:1
- error.parse.static_error_bad_number.jsonnet:17:1-1: parse imported
\ No newline at end of file
+ error.parse.static_error_bad_number.jsonnet:17:1: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: invalid string escape
- error.parse.string.invalid_escape.jsonnet:17:1
+ error.parse.string.invalid_escape.jsonnet:17:1-4
error.parse.string.invalid_escape.jsonnet:17:1-4: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_non_hex.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_non_hex.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_non_hex.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: invalid string escape
- error.parse.string.invalid_escape_unicode_non_hex.jsonnet:17:1
+ error.parse.string.invalid_escape_unicode_non_hex.jsonnet:17:1-8
error.parse.string.invalid_escape_unicode_non_hex.jsonnet:17:1-8: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unterminated double-quoted string
- error.parse.string.invalid_escape_unicode_short.jsonnet:17:1
- error.parse.string.invalid_escape_unicode_short.jsonnet:17:1-18:0: parse imported
\ No newline at end of file
+ error.parse.string.invalid_escape_unicode_short.jsonnet:17:1-18:1
+ error.parse.string.invalid_escape_unicode_short.jsonnet:17:1-18:1: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short2.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short2.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short2.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: invalid string escape
- error.parse.string.invalid_escape_unicode_short2.jsonnet:17:1
+ error.parse.string.invalid_escape_unicode_short2.jsonnet:17:1-7
error.parse.string.invalid_escape_unicode_short2.jsonnet:17:1-7: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short3.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short3.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short3.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unterminated double-quoted string
- error.parse.string.invalid_escape_unicode_short3.jsonnet:17:1
- error.parse.string.invalid_escape_unicode_short3.jsonnet:17:1-18:0: parse imported
\ No newline at end of file
+ error.parse.string.invalid_escape_unicode_short3.jsonnet:17:1-18:1
+ error.parse.string.invalid_escape_unicode_short3.jsonnet:17:1-18:1: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.unfinished.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.unfinished.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.unfinished.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unterminated double-quoted string
- error.parse.string.unfinished.jsonnet:17:1
- error.parse.string.unfinished.jsonnet:17:1-18:0: parse imported
\ No newline at end of file
+ error.parse.string.unfinished.jsonnet:17:1-18:1
+ error.parse.string.unfinished.jsonnet:17:1-18:1: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.unfinished2.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.unfinished2.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.unfinished2.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unterminated single-quoted string
- error.parse.string.unfinished2.jsonnet:17:1
- error.parse.string.unfinished2.jsonnet:17:1-18:0: parse imported
\ No newline at end of file
+ error.parse.string.unfinished2.jsonnet:17:1-18:1
+ error.parse.string.unfinished2.jsonnet:17:1-18:1: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string_multi_no_newline.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string_multi_no_newline.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string_multi_no_newline.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: text block requires new line after |||
- error.parse.string_multi_no_newline.jsonnet:17:1
- error.parse.string_multi_no_newline.jsonnet:17:1-18:0: parse imported
\ No newline at end of file
+ error.parse.string_multi_no_newline.jsonnet:17:1-18:1
+ error.parse.string_multi_no_newline.jsonnet:17:1-18:1: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.text_block_bad_whitespace.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.text_block_bad_whitespace.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.text_block_bad_whitespace.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unterminated text block
- error.parse.text_block_bad_whitespace.jsonnet:17:1
+ error.parse.text_block_bad_whitespace.jsonnet:17:1-20:3
error.parse.text_block_bad_whitespace.jsonnet:17:1-20:3: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.text_block_eof.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.text_block_eof.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.text_block_eof.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unexpected end of text block
- error.parse.text_block_eof.jsonnet:17:1
+ error.parse.text_block_eof.jsonnet:17:1-18:6
error.parse.text_block_eof.jsonnet:17:1-18:6: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.text_block_indent_spaces.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.text_block_indent_spaces.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.text_block_indent_spaces.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unterminated text block
- error.parse.text_block_indent_spaces.jsonnet:17:1
+ error.parse.text_block_indent_spaces.jsonnet:17:1-20:3
error.parse.text_block_indent_spaces.jsonnet:17:1-20:3: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.text_block_not_terminated.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.text_block_not_terminated.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.text_block_not_terminated.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unexpected end of text block
- error.parse.text_block_not_terminated.jsonnet:17:1
- error.parse.text_block_not_terminated.jsonnet:17:1-19:0: parse imported
\ No newline at end of file
+ error.parse.text_block_not_terminated.jsonnet:17:1-19:1
+ error.parse.text_block_not_terminated.jsonnet:17:1-19:1: parse imported
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.static_error_self.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.static_error_self.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.static_error_self.jsonnet.golden
@@ -1 +1,2 @@
-static analysis errors: `self` used outside of object
\ No newline at end of file
+static analysis errors
+ error.static_error_self.jsonnet:17:2-5 error: `self` used outside of object
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.static_error_super.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.static_error_super.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.static_error_super.jsonnet.golden
@@ -1 +1,2 @@
-static analysis errors: `super` used outside of object
\ No newline at end of file
+static analysis errors
+ error.static_error_super.jsonnet:17:2-6 error: `super` used outside of object
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.static_error_var_not_exist.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.static_error_var_not_exist.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.static_error_var_not_exist.jsonnet.golden
@@ -1,2 +1,4 @@
-static analysis errors: undefined local: tmp2
-There is a local with similar name present: tmp; unused local: tmp
\ No newline at end of file
+static analysis errors
+ error.static_error_var_not_exist.jsonnet:17:16-19 error: undefined local: tmp2
+ There is a local with similar name present: tmp
+ error.static_error_var_not_exist.jsonnet:17:7-9 warning: unused local: tmp
\ No newline at end of file
tests/go_testdata_golden_override/arrcomp5.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/arrcomp5.jsonnet.golden
+++ b/tests/go_testdata_golden_override/arrcomp5.jsonnet.golden
@@ -1 +1,3 @@
-static analysis errors: undefined local: x; unused local: x
\ No newline at end of file
+static analysis errors
+ arrcomp5.jsonnet:1:14 error: undefined local: x
+ arrcomp5.jsonnet:1:25 warning: unused local: x
\ No newline at end of file
tests/go_testdata_golden_override/arrcomp_if4.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/arrcomp_if4.jsonnet.golden
+++ b/tests/go_testdata_golden_override/arrcomp_if4.jsonnet.golden
@@ -1 +1,3 @@
-static analysis errors: undefined local: y; local could be hoisted to an outer scope: y
\ No newline at end of file
+static analysis errors
+ arrcomp_if4.jsonnet:1:33 error: undefined local: y
+ arrcomp_if4.jsonnet:1:44 warning: local could be hoisted to an outer scope: y
\ No newline at end of file
tests/go_testdata_golden_override/builtinObjectRemoveKey_super_assert.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/builtinObjectRemoveKey_super_assert.jsonnet.golden
+++ b/tests/go_testdata_golden_override/builtinObjectRemoveKey_super_assert.jsonnet.golden
@@ -1,3 +1,3 @@
no such field: x
- builtinObjectRemoveKey_super_assert.jsonnet:2:15-15: field <x> access
+ builtinObjectRemoveKey_super_assert.jsonnet:2:15: field <x> access
builtinObjectRemoveKey_super_assert.jsonnet:2:10-15: assertion condition
\ No newline at end of file
tests/go_testdata_golden_override/dollar_bad.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/dollar_bad.jsonnet.golden
+++ b/tests/go_testdata_golden_override/dollar_bad.jsonnet.golden
@@ -1 +1,2 @@
-static analysis errors: `$` used outside of object
\ No newline at end of file
+static analysis errors
+ dollar_bad.jsonnet:1:1 error: `$` used outside of object
\ No newline at end of file
tests/go_testdata_golden_override/error_from_array.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/error_from_array.jsonnet.golden
+++ b/tests/go_testdata_golden_override/error_from_array.jsonnet.golden
@@ -1,3 +1,3 @@
runtime error: xxx
- error_from_array.jsonnet:1:2-6: error statement
- error_from_array.jsonnet:1:15-15: element <0> access
\ No newline at end of file
+ error_from_array.jsonnet:1:2-6: error statement
+ error_from_array.jsonnet:1:15: element <0> access
\ No newline at end of file
tests/go_testdata_golden_override/error_hexnumber.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/error_hexnumber.jsonnet.golden
+++ b/tests/go_testdata_golden_override/error_hexnumber.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: expected end of file, got identifier "x42"
- error_hexnumber.jsonnet:1:2
+ error_hexnumber.jsonnet:1:2-4
error_hexnumber.jsonnet:1:2-4: parse imported
\ No newline at end of file
tests/go_testdata_golden_override/import_computed.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/import_computed.jsonnet.golden
+++ b/tests/go_testdata_golden_override/import_computed.jsonnet.golden
@@ -1 +1,2 @@
-static analysis errors: import path must be a string literal
\ No newline at end of file
+static analysis errors
+ import_computed.jsonnet:1:1-6 error: import path must be a string literal
\ No newline at end of file
tests/go_testdata_golden_override/import_syntax_error.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/import_syntax_error.jsonnet.golden
+++ b/tests/go_testdata_golden_override/import_syntax_error.jsonnet.golden
@@ -1,4 +1,4 @@
syntax error: unexpected end of file
syntax_error.jsonnet:1:4
- syntax_error.jsonnet:1:4-0:0: parse imported
+ syntax_error.jsonnet:1:4: parse imported
import_syntax_error.jsonnet:1:1-6: import
\ No newline at end of file
tests/go_testdata_golden_override/importbin_computed.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/importbin_computed.jsonnet.golden
+++ b/tests/go_testdata_golden_override/importbin_computed.jsonnet.golden
@@ -1 +1,2 @@
-static analysis errors: import path must be a string literal
\ No newline at end of file
+static analysis errors
+ importbin_computed.jsonnet:1:1-9 error: import path must be a string literal
\ No newline at end of file
tests/go_testdata_golden_override/importstr_computed.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/importstr_computed.jsonnet.golden
+++ b/tests/go_testdata_golden_override/importstr_computed.jsonnet.golden
@@ -1 +1,2 @@
-static analysis errors: import path must be a string literal
\ No newline at end of file
+static analysis errors
+ importstr_computed.jsonnet:1:1-9 error: import path must be a string literal
\ No newline at end of file
tests/go_testdata_golden_override/insuper4.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/insuper4.jsonnet.golden
+++ b/tests/go_testdata_golden_override/insuper4.jsonnet.golden
@@ -1 +1,2 @@
-static analysis errors: `super` used outside of object
\ No newline at end of file
+static analysis errors
+ insuper4.jsonnet:1:8-12 error: `super` used outside of object
\ No newline at end of file
tests/go_testdata_golden_override/insuper6.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/insuper6.jsonnet.golden
+++ b/tests/go_testdata_golden_override/insuper6.jsonnet.golden
@@ -1 +1,2 @@
-static analysis errors: undefined local: undeclared
\ No newline at end of file
+static analysis errors
+ insuper6.jsonnet:1:10-19 error: undefined local: undeclared
\ No newline at end of file
tests/go_testdata_golden_override/number_leading_zero.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/number_leading_zero.jsonnet.golden
+++ b/tests/go_testdata_golden_override/number_leading_zero.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: expected end of file, got number "42"
- number_leading_zero.jsonnet:1:2
+ number_leading_zero.jsonnet:1:2-3
number_leading_zero.jsonnet:1:2-3: parse imported
\ No newline at end of file
tests/go_testdata_golden_override/object_comp_assert.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/object_comp_assert.jsonnet.golden
+++ b/tests/go_testdata_golden_override/object_comp_assert.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: asserts are unsupported in object comprehension
object_comp_assert.jsonnet:1:46
- object_comp_assert.jsonnet:1:46-46: parse imported
\ No newline at end of file
+ object_comp_assert.jsonnet:1:46: parse imported
\ No newline at end of file
tests/go_testdata_golden_override/object_comp_illegal.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/object_comp_illegal.jsonnet.golden
+++ b/tests/go_testdata_golden_override/object_comp_illegal.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: missing object comprehension field
object_comp_illegal.jsonnet:1:34
- object_comp_illegal.jsonnet:1:34-34: parse imported
\ No newline at end of file
+ object_comp_illegal.jsonnet:1:34: parse imported
\ No newline at end of file
tests/go_testdata_golden_override/object_invariant11.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/object_invariant11.jsonnet.golden
+++ b/tests/go_testdata_golden_override/object_invariant11.jsonnet.golden
@@ -1,3 +1,3 @@
assert failed: null
object_invariant11.jsonnet:1:10-14: assertion failure
- object_invariant11.jsonnet:1:18-18: field <x> access
\ No newline at end of file
+ object_invariant11.jsonnet:1:18: field <x> access
\ No newline at end of file
tests/go_testdata_golden_override/static_error_eof.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/static_error_eof.jsonnet.golden
+++ b/tests/go_testdata_golden_override/static_error_eof.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: expected ';', got end of file
static_error_eof.jsonnet:1:12
- static_error_eof.jsonnet:1:12-0:0: parse imported
\ No newline at end of file
+ static_error_eof.jsonnet:1:12: parse imported
\ No newline at end of file
tests/go_testdata_golden_override/std.codepoint7.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/std.codepoint7.jsonnet.golden
+++ b/tests/go_testdata_golden_override/std.codepoint7.jsonnet.golden
@@ -1,3 +1,3 @@
type error: expected char, got string
argument <str> evaluation
- std.codepoint7.jsonnet:2:14-0:0: function <builtin_codepoint> call
\ No newline at end of file
+ std.codepoint7.jsonnet:2:14-0:14: function <builtin_codepoint> call
\ No newline at end of file
tests/go_testdata_golden_override/syntax_error.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/syntax_error.jsonnet.golden
+++ b/tests/go_testdata_golden_override/syntax_error.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: unexpected end of file
syntax_error.jsonnet:1:4
- syntax_error.jsonnet:1:4-0:0: parse imported
\ No newline at end of file
+ syntax_error.jsonnet:1:4: parse imported
\ No newline at end of file
tests/go_testdata_golden_override/unfinished_args.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/unfinished_args.jsonnet.golden
+++ b/tests/go_testdata_golden_override/unfinished_args.jsonnet.golden
@@ -1,3 +1,3 @@
syntax error: expected ')', got end of file
unfinished_args.jsonnet:1:17
- unfinished_args.jsonnet:1:17-0:0: parse imported
\ No newline at end of file
+ unfinished_args.jsonnet:1:17: parse imported
\ No newline at end of file
tests/go_testdata_golden_override/variable_not_visible.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/variable_not_visible.jsonnet.golden
+++ b/tests/go_testdata_golden_override/variable_not_visible.jsonnet.golden
@@ -1 +1,3 @@
-static analysis errors: undefined local: nested; unused local: x1
\ No newline at end of file
+static analysis errors
+ variable_not_visible.jsonnet:1:44-49 error: undefined local: nested
+ variable_not_visible.jsonnet:1:7-8 warning: unused local: x1
\ No newline at end of file
tests/tests/snapshots/golden__golden@issue172.jsonnet.snapdiffbeforeafterboth--- a/tests/tests/snapshots/golden__golden@issue172.jsonnet.snap
+++ b/tests/tests/snapshots/golden__golden@issue172.jsonnet.snap
@@ -3,4 +3,5 @@
expression: result
input_file: tests/golden/issue172.jsonnet
---
-static analysis errors: undefined local: b
+static analysis errors
+ issue172.jsonnet:1:45 error: undefined local: b
tests/tests/snapshots/golden__golden@missing_binding.jsonnet.snapdiffbeforeafterboth--- a/tests/tests/snapshots/golden__golden@missing_binding.jsonnet.snap
+++ b/tests/tests/snapshots/golden__golden@missing_binding.jsonnet.snap
@@ -3,5 +3,6 @@
expression: result
input_file: tests/golden/missing_binding.jsonnet
---
-static analysis errors: undefined local: sta
-There is a local with similar name present: std
+static analysis errors
+ missing_binding.jsonnet:1:1-3 error: undefined local: sta
+ There is a local with similar name present: std
xtask/src/sourcegen/kinds.rsdiffbeforeafterboth--- a/xtask/src/sourcegen/kinds.rs
+++ b/xtask/src/sourcegen/kinds.rs
@@ -294,7 +294,7 @@
lit("WHITESPACE") => r"[ \t\n\r]+";
lit("SINGLE_LINE_SLASH_COMMENT") => r"//[^\r\n]*?(\r\n|\n)?";
lit("SINGLE_LINE_HASH_COMMENT") => r"#[^\r\n]*?(\r\n|\n)?";
- lit("MULTI_LINE_COMMENT") => r"/\*([^*]|\*[^/])*\*/";
+ lit("MULTI_LINE_COMMENT") => r"/\*([^*]|\*+[^*/])*\*+/";
error("COMMENT_TOO_SHORT", "comment too short") => r"/\*/";
error("COMMENT_UNTERMINATED", "unterminated multi-line comment") => r"/\*([^*/]|\*[^/])+";
error("NO_OPERATOR", "expected operator");