From 40875769025f1ea7484f97ee489a2f534cb85fad Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 07 May 2026 21:10:06 +0000 Subject: [PATCH] refactor: better static analysis error display --- --- 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, + 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) } --- a/crates/jrsonnet-evaluator/src/trace/mod.rs +++ b/crates/jrsonnet-evaluator/src/trace/mod.rs @@ -7,11 +7,9 @@ }; use jrsonnet_gcmodule::Trace; -use jrsonnet_ir::CodeLocation; -#[cfg(feature = "explaining-traces")] -use jrsonnet_ir::Span; +use jrsonnet_ir::{CodeLocation, Span}; -use crate::{Error, ResolvePathOwned, error::ErrorKind}; +use crate::{Error, ResolvePathOwned, analyze::DiagLevel, error::ErrorKind}; /// The way paths should be displayed #[derive(Clone, Trace)] @@ -72,31 +70,64 @@ fn as_any_mut(&mut self) -> &mut dyn Any; } +fn span_label(resolver: &PathResolver, span: &Span) -> String { + use std::fmt::Write; + let mut path = span + .0 + .source_path() + .path() + .map_or_else(|| span.0.source_path().to_string(), |p| resolver.resolve(p)); + #[expect(clippy::cast_possible_truncation, reason = "code is limited by 4gb")] + let len = span.0.code().len() as u32; + let start = span.1.min(len); + let end = span.2.min(len); + let (start_loc, end_loc) = if start == end { + let [loc] = span.0.map_source_locations(&[start]); + (loc, loc) + } else { + let [s, e] = span.0.map_source_locations(&[start, end]); + (s, e) + }; + write!(path, ":").unwrap(); + print_code_location(&mut path, &start_loc, &end_loc).unwrap(); + path +} + +#[cfg(feature = "explaining-traces")] +fn span_render_range(span: &Span) -> Option> { + let len = span.0.code().len(); + if len == 0 { + return None; + } + let max = len - 1; + let r = span.range(); + Some((*r.start()).min(max)..=(*r.end()).min(max)) +} + +fn diag_level_label(level: DiagLevel) -> &'static str { + match level { + DiagLevel::Error => "error", + DiagLevel::Warning => "warning", + } +} + fn print_code_location( out: &mut impl fmt::Write, start: &CodeLocation, end: &CodeLocation, ) -> Result<(), fmt::Error> { + let end_col = end.column.saturating_sub(1).max(start.column); if start.line == end.line { - if start.column == end.column { + if start.column == end_col { write!(out, "{}:{}", start.line, start.column)?; } else { - write!( - out, - "{}:{}-{}", - start.line, - start.column, - end.column.saturating_sub(1) - )?; + write!(out, "{}:{}-{}", start.line, start.column, end_col)?; } } else { write!( out, "{}:{}-{}:{}", - start.line, - start.column, - end.line, - end.column.saturating_sub(1) + start.line, start.column, end.line, end_col )?; } Ok(()) @@ -121,61 +152,63 @@ impl TraceFormat for CompactFormat { fn write_trace(&self, out: &mut dyn fmt::Write, error: &Error) -> Result<(), fmt::Error> { - if let ErrorKind::ImportFileNotFound(from, import) = error.error() { - let from = from - .path() - .map_or_else(|| from.to_string(), |path| self.resolver.resolve(path)); - let import = match import { - ResolvePathOwned::Str(s) => s.clone(), - ResolvePathOwned::Path(path_buf) => self.resolver.resolve(path_buf), - }; - write!(out, "import file not found {import} from {from}")?; - } else { - write!(out, "{}", error.error())?; + match error.error() { + ErrorKind::ImportFileNotFound(from, import) => { + let from = from + .path() + .map_or_else(|| from.to_string(), |path| self.resolver.resolve(path)); + let import = match import { + ResolvePathOwned::Str(s) => s.clone(), + ResolvePathOwned::Path(path_buf) => self.resolver.resolve(path_buf), + }; + write!(out, "import file not found {import} from {from}")?; + } + ErrorKind::StaticAnalysisError(_) => { + write!(out, "static analysis errors")?; + } + _ => { + write!(out, "{}", error.error())?; + } } - if let ErrorKind::ImportSyntaxError { path, error } = error.error() { - use std::fmt::Write; + if let ErrorKind::StaticAnalysisError(diagnostics) = error.error() { + let labels: Vec> = diagnostics + .iter() + .map(|d| d.span.as_ref().map(|s| span_label(&self.resolver, s))) + .collect(); + let align = labels.iter().flatten().map(String::len).max().unwrap_or(0); + let cont_indent = " ".repeat(self.padding + align + 1); + for (diag, label) in diagnostics.iter().zip(labels.iter()) { + writeln!(out)?; + let level = diag_level_label(diag.level); + let message = diag.message.replace('\n', &format!("\n{cont_indent}")); + let label = label.as_deref().unwrap_or(""); + write!( + out, + "{: self.resolver.resolve(r), - None => location.0.source_path().to_string(), - }; - // TODO: Process all trace elements first - let location = location.0.map_source_locations(&[location.1, location.2]); - write!(resolved_path, ":").unwrap(); - print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap(); - write!(resolved_path, ":").unwrap(); - Some(resolved_path) - } else { - None - } + .map(|el| { + el.location.as_ref().map(|loc| { + use std::fmt::Write; + let mut s = span_label(&self.resolver, loc); + write!(s, ":").unwrap(); + s + }) }) .collect::>(); let align = file_names @@ -265,40 +298,45 @@ } use hi_doc::{Formatting, SnippetBuilder, Text, source_to_ansi}; - write!(out, "{}", error.error())?; + match error.error() { + ErrorKind::StaticAnalysisError(_) => write!(out, "static analysis errors")?, + _ => write!(out, "{}", error.error())?, + } if let ErrorKind::ImportSyntaxError { path, error } = error.error() { - writeln!(out)?; - let mut builder = SnippetBuilder::new(path.code()); - builder - .error(Text::fragment("syntax error", Formatting::default())) - .range(error.location.range()) - .build(); - let source = builder.build(); - let ansi = source_to_ansi(&source); - write!(out, "{ansi}")?; + writeln!(out, "\n...at {}", path.source_path())?; + if let Some(range) = span_render_range(&error.location) { + let mut builder = SnippetBuilder::new(path.code()); + builder + .error(Text::fragment("syntax error", Formatting::default())) + .range(range) + .build(); + let ansi = source_to_ansi(&builder.build()); + write!(out, "{}", ansi.trim_end())?; + } } if let ErrorKind::StaticAnalysisError(diagnostics) = error.error() { - use crate::analyze::DiagLevel; - let mut builder: Option = None; - let mut current_src: Option<&str> = None; - let flush = |builder: Option, + let mut builder: Option<(SnippetBuilder, Span)> = None; + let flush = |slot: Option<(SnippetBuilder, Span)>, out: &mut dyn fmt::Write| -> Result<(), fmt::Error> { - if let Some(b) = builder { + if let Some((b, anchor)) = slot { + writeln!(out, "\n...at {}", anchor.0.source_path())?; let ansi = source_to_ansi(&b.build()); - write!(out, "\n{}", ansi.trim_end())?; + write!(out, "{}", ansi.trim_end())?; } Ok(()) }; for diag in diagnostics { if let Some(span) = &diag.span { - let src = span.0.code(); - if current_src != Some(src) { + let Some(range) = span_render_range(span) else { + continue; + }; + let same_src = builder.as_ref().is_some_and(|(_, a)| a.0 == span.0); + if !same_src { flush(builder.take(), out)?; - builder = Some(SnippetBuilder::new(src)); - current_src = Some(src); + builder = Some((SnippetBuilder::new(span.0.code()), span.clone())); } - let b = builder.as_mut().unwrap(); + let b = &mut builder.as_mut().unwrap().0; let ab = match diag.level { DiagLevel::Error => { b.error(Text::fragment(diag.message.clone(), Formatting::default())) @@ -307,14 +345,10 @@ b.warning(Text::fragment(diag.message.clone(), Formatting::default())) } }; - ab.range(span.range()).build(); + ab.range(range).build(); } else { flush(builder.take(), out)?; - current_src = None; - let prefix = match diag.level { - DiagLevel::Error => "error", - DiagLevel::Warning => "warning", - }; + let prefix = diag_level_label(diag.level); write!(out, "\n{prefix}: {}", diag.message)?; } } --- 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 { + 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 { +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 { @@ -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 { - 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), --- /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::6-7, + params: ExprParams { + exprs: [ + ExprParam { + destruct: Full( + "x" from virtual::8-9, + ), + default: None, + }, + ExprParam { + destruct: Full( + "y" from virtual::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::18-19, + ), + op: Add, + rhs: Var( + "y" from virtual::22-23, + ), + }, + ), + }, + ], + Apply( + Var( + "f" from virtual::25-26, + ), + ArgsDesc { + unnamed: [ + Num( + 2.0, + ), + ], + names: [ + "y", + ], + values: [ + Num( + 3.0, + ), + ], + } from virtual::26-34, + false, + ), +) --- /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::6-7, + params: ExprParams { + exprs: [ + ExprParam { + destruct: Full( + "foo" from virtual::8-11, + ), + default: Some( + Str( + "foo", + ), + ), + }, + ExprParam { + destruct: Full( + "bar" from virtual::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, + ), +) --- 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, 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, }, /// function(x) x - Function(ExprParams, Box), + Function(Span, ExprParams, Box), /// if true == false then 1 else 2 IfElse(Box), Slice(Box), --- a/crates/jrsonnet-ir/src/visit.rs +++ b/crates/jrsonnet-ir/src/visit.rs @@ -178,7 +178,7 @@ } pub fn visit_expr(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); } --- 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(, s) _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {BindSpec::Function{name, params, value}} pub rule assertion(s: &ParserSettings) -> AssertStmt = keyword("assert") _ assertion:spanned(, 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(, 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 })) } --- 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 access \ No newline at end of file + error.03.jsonnet:18:8: field access \ No newline at end of file --- 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 call \ No newline at end of file --- 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 --- 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 --- 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 access \ No newline at end of file + error.field_not_exist.jsonnet:17:10: field access \ No newline at end of file --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 access + builtinObjectRemoveKey_super_assert.jsonnet:2:15: field access builtinObjectRemoveKey_super_assert.jsonnet:2:10-15: assertion condition \ No newline at end of file --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 access \ No newline at end of file + object_invariant11.jsonnet:1:18: field access \ No newline at end of file --- 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 --- 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 evaluation - std.codepoint7.jsonnet:2:14-0:0: function call \ No newline at end of file + std.codepoint7.jsonnet:2:14-0:14: function call \ No newline at end of file --- 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 --- 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 --- 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 --- 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 --- 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 --- 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"); -- gitstuff