12#![cfg(any())]34use miette::{5 Diagnostic, GraphicalReportHandler, GraphicalTheme, LabeledSpan, ThemeCharacters, ThemeStyles,6};7use thiserror::Error;89use crate::{parse, AstNode};1011#[derive(Debug, Error)]12#[error("syntax error")]13struct MyDiagnostic {14 code: String,15 spans: Vec<LabeledSpan>,16}17impl Diagnostic for MyDiagnostic {18 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {19 None20 }2122 fn severity(&self) -> Option<miette::Severity> {23 None24 }2526 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {27 None28 }2930 fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {31 None32 }3334 fn source_code(&self) -> Option<&dyn miette::SourceCode> {35 Some(&self.code)36 }3738 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {39 Some(Box::new(self.spans.clone().into_iter()))40 }4142 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {43 None44 }45}4647fn process(text: &str) -> String {48 use std::fmt::Write;49 let mut out = String::new();50 let (node, errors) = parse(text);51 write!(out, "{:#?}", node.syntax()).unwrap();52 if !errors.is_empty() && !text.is_empty() {53 writeln!(out, "===").unwrap();54 for err in &errors {55 writeln!(out, "{:?}", err).unwrap();56 }57 let mut code = text.to_string();5859 60 if code.ends_with('\n') {61 code.truncate(code.len() - 1);62 code += " ";63 }64 code += " ";6566 let diag = MyDiagnostic {67 code,68 spans: errors.into_iter().map(|e| e.into()).collect(),69 };7071 let handler = GraphicalReportHandler::new_themed(GraphicalTheme {72 characters: ThemeCharacters::ascii(),73 styles: ThemeStyles::none(),74 });7576 writeln!(out, "===").unwrap();77 handler78 .render_report(&mut out, &diag)79 .expect("fmt error?..");80 }81 out.split('\n')82 .map(|s| s.trim_end().to_string())83 .collect::<Vec<String>>()84 .join("\n")85 .trim_end()86 .to_string()87}88macro_rules! mk_test {89 ($($name:ident => $test:expr)+) => {$(90 #[test]91 fn $name() {92 let src = indoc::indoc!($test);93 let result = process(&src);94 insta::assert_snapshot!(stringify!($name), result, src);9596 }97 )+};98 }99mk_test!(100 empty => r#" "#101 function => r#"102 function(a, b = 1) a + b103 "#104 function_error_no_value => r#"105 function(a, b = ) a + b106 "#107 function_error_rparen => r#"108 function(a, b109 "#110 function_error_body => r#"111 function(a, b)112 "#113 local_novalue => r#"114 local a =115 "#116 local_no_value_recovery => r#"117 local a =118 local b = 3;119 1120 "#121122123 no_rhs => r#"124 a +125 "#126 no_lhs => r#"127 + 2128 "#129 no_operator => "130 2 2131 "132133 named_before_positional => "134 a(1, 2, b=4, 3, 5, k = 12, 6)135 "136137 wrong_field_end => "138 {139 a: 1;140 b: 2;141 }142 "143144145 plain_call => "146 std.substr(a, 0, std.length(b)) == b147 "148149 destruct => "150 local [a, b, c] = arr;151 local [a, ...] = arr_rest;152 local [..., a] = rest_arr;153 local [...] = rest_in_arr;154 local [a, ...n] = arr_rest_n;155 local [...n, a] = rest_arr_n;156 local [...n] = rest_in_arr_n;157158 local {a, b, c} = obj;159 local {a, b, c, ...} = obj_rest;160 local {a, b, c, ...n} = obj_rest_n;161162 null163 "164165 str_block_missing_indent => "166 |||167 "168 str_block_missing_termination => "169 |||170 hello171 "172 str_block_missing_newline => "173 |||hello174 "175 str_block_missing_indent_text => "176 |||177 hello178 "179180 unexpected_destruct => "181 local * = 1;182 a183 "184 arr_compspec => r#"185 [a for a in [1, 2, 3]]186 "#187 arr_compspec_comma => "188 [a, for a in [1, 2, 3]]189 "190 arr_compspec_no_elems => "191 [for a in [1, 2, 3]]192 "193 arr_compspec_incompatible_with_multiple_elems => r#"194 [a for a in [1, 2, 3], b]195 "#196 arr_compspec_incompatible_with_multiple_elems_w => r#"197 [a, b, for a in [1, 2, 3], c]198 "#199200 obj_compspec => r#"201 {a:1 for a in [1, 2, 3]}202 "#203 obj_compspec_comma => "204 {a:1, for a in [1, 2, 3]}205 "206 obj_compspec_no_elems => "207 {for a in [1, 2, 3]}208 "209 obj_compspec_incompatible_with_multiple_elems => r#"210 {a:1 for a in [1, 2, 3], b:1}211 "#212 obj_compspec_incompatible_with_multiple_elems_w => r#"213 {a:1, b:1, for a in [1, 2, 3], c:1}214 "#215216 obj_compspec_incompatible_with_asserts => r#"217 {assert 1, a: 1 for a in [1,2,3]}218 "#219220 local_method => r#"221 local222 a(x) = x,223 a = function(x) x,224 ; c225 "#226 obj_method => r#"227 {228 a(x): x,229 a: function(x) x,230 }231 "#232233 continue_after_total_failure => r#"234 local intr = $intrinsic(test);235236 local a = 1, b = 2, c = a + b;237238 [c]239 "#240);241242#[test]243fn stdlib() {244 let src = include_str!("../../jrsonnet-stdlib/src/std.jsonnet");245 let result = process(src);246 insta::assert_snapshot!("stdlib", result, src);247}248#[test]249fn eval_simple() {250 let src = "local a = 1, b = 2; a + local c = 1; c";251 let (node, _errors) = parse(src);252253 dbg!(node);254}