1#![cfg(test)]23use miette::{4 Diagnostic, GraphicalReportHandler, GraphicalTheme, LabeledSpan, ThemeCharacters, ThemeStyles,5};6use thiserror::Error;78use crate::{parse, AstNode};910#[derive(Debug, Error)]11#[error("syntax error")]12struct MyDiagnostic {13 code: String,14 spans: Vec<LabeledSpan>,15}16impl Diagnostic for MyDiagnostic {17 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {18 None19 }2021 fn severity(&self) -> Option<miette::Severity> {22 None23 }2425 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {26 None27 }2829 fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {30 None31 }3233 fn source_code(&self) -> Option<&dyn miette::SourceCode> {34 Some(&self.code)35 }3637 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {38 Some(Box::new(self.spans.clone().into_iter()))39 }4041 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {42 None43 }44}4546fn process(text: &str) -> String {47 use std::fmt::Write;48 let mut out = String::new();49 let (node, errors) = parse(text);50 write!(out, "{:#?}", node.syntax()).unwrap();51 if !errors.is_empty() && !text.is_empty() {52 writeln!(out, "===").unwrap();53 for err in &errors {54 writeln!(out, "{:?}", err).unwrap();55 }56 let mut code = text.to_string();5758 59 if code.ends_with('\n') {60 code.truncate(code.len() - 1);61 code += " ";62 }63 code += " ";6465 let diag = MyDiagnostic {66 code,67 spans: errors.into_iter().map(|e| e.into()).collect(),68 };6970 let handler = GraphicalReportHandler::new_themed(GraphicalTheme {71 characters: ThemeCharacters::ascii(),72 styles: ThemeStyles::none(),73 });7475 write!(out, "===").unwrap();76 handler77 .render_report(&mut out, &diag)78 .expect("fmt error?..");79 }80 out.split('\n')81 .map(|s| s.trim_end().to_string())82 .collect::<Vec<String>>()83 .join("\n")84 .trim_end()85 .to_string()86}87macro_rules! mk_test {88 ($($name:ident => $test:expr)+) => {$(89 #[test]90 fn $name() {91 let src = indoc::indoc!($test);92 let result = process(&src);93 insta::assert_snapshot!(stringify!($name), result, src);9495 }96 )+};97 }98mk_test!(99 empty => r#" "#100 function => r#"101 function(a, b = 1) a + b102 "#103 function_error_no_value => r#"104 function(a, b = ) a + b105 "#106 function_error_rparen => r#"107 function(a, b108 "#109 function_error_body => r#"110 function(a, b)111 "#112 local_novalue => r#"113 local a =114 "#115 local_no_value_recovery => r#"116 local a =117 local b = 3;118 1119 "#120121 array_comp => r#"122 [a for a in [1, 2, 3]]123 "#124 array_comp_incompatible_with_multiple_elems => r#"125 [a for a in [1, 2, 3], b]126 "#127128 no_rhs => r#"129 a +130 "#131 no_lhs => r#"132 + 2133 "#134 no_operator => "135 2 2136 "137138 named_before_positional => "139 a(1, 2, b=4, 3, 5, k = 12, 6)140 "141142 wrong_field_end => "143 {144 a: 1;145 b: 2;146 }147 "148149150 plain_call => "151 std.substr(a, 0, std.length(b)) == b152 "153154 destruct => "155 local [a, b, c] = arr;156 local [a, ...] = arr_rest;157 local [..., a] = rest_arr;158 local [...] = rest_in_arr;159 local [a, ...n] = arr_rest_n;160 local [...n, a] = rest_arr_n;161 local [...n] = rest_in_arr_n;162163 local {a, b, c} = obj;164 local {a, b, c, ...} = obj_rest;165 local {a, b, c, ...n} = obj_rest_n;166167 null168 "169170 str_block_missing_indent => "171 |||172 "173 str_block_missing_termination => "174 |||175 hello176 "177 str_block_missing_newline => "178 |||hello179 "180 str_block_missing_indent_text => "181 |||182 hello183 "184185 unexpected_destruct => "186 local * = 1;187 a188 "189);190191#[test]192fn stdlib() {193 let src = include_str!("../../jrsonnet-stdlib/src/std.jsonnet");194 let result = process(src);195 insta::assert_snapshot!("stdlib", result, src);196}