difftreelog
fix(rowan-parser) forbid assert in objcomp
in: master
3 files changed
crates/jrsonnet-rowan-parser/src/parser.rsdiffbeforeafterboth--- a/crates/jrsonnet-rowan-parser/src/parser.rs
+++ b/crates/jrsonnet-rowan-parser/src/parser.rs
@@ -408,6 +408,7 @@
let mut elems = 0;
let mut compspecs = Vec::new();
+ let mut asserts = Vec::new();
loop {
if p.at(T!['}']) {
p.bump();
@@ -430,10 +431,10 @@
let m = p.start();
if p.at(T![local]) {
obj_local(p);
- m.complete(p, MEMBER_BIND_STMT)
+ m.complete(p, MEMBER_BIND_STMT);
} else if p.at(T![assert]) {
assertion(p);
- m.complete(p, MEMBER_ASSERT_STMT)
+ asserts.push(m.complete(p, MEMBER_ASSERT_STMT));
} else {
field_name(p);
if p.at(T![+]) {
@@ -455,14 +456,14 @@
expr(p);
false
};
+ elems += 1;
if params {
m.complete(p, MEMBER_FIELD_METHOD)
} else {
m.complete(p, MEMBER_FIELD_NORMAL)
- }
+ };
};
- elems += 1;
while p.at_ts(COMPSPEC) {
compspecs.push(compspec(p));
}
@@ -482,6 +483,9 @@
}
m.complete(p, OBJ_BODY_MEMBER_LIST);
} else if !compspecs.is_empty() {
+ for errored in asserts {
+ errored.wrap_error(p, "asserts can't be used in object comprehensions");
+ }
m.complete(p, OBJ_BODY_COMP);
} else {
m.complete(p, OBJ_BODY_MEMBER_LIST);
crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_incompatible_with_asserts.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_incompatible_with_asserts.snap
@@ -0,0 +1,57 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{assert 1, a: 1 for a in [1,2,3]}\n"
+---
+SOURCE_FILE@0..34
+ EXPR_OBJECT@0..33
+ OBJ_BODY_COMP@0..33
+ L_BRACE@0..1 "{"
+ ERROR_CUSTOM@1..9
+ MEMBER_ASSERT_STMT@1..9
+ ASSERTION@1..9
+ ASSERT_KW@1..7 "assert"
+ WHITESPACE@7..8 " "
+ LHS_EXPR@8..9
+ EXPR_NUMBER@8..9
+ FLOAT@8..9 "1"
+ COMMA@9..10 ","
+ WHITESPACE@10..11 " "
+ MEMBER_FIELD_NORMAL@11..15
+ FIELD_NAME_FIXED@11..12
+ NAME@11..12
+ IDENT@11..12 "a"
+ COLON@12..13 ":"
+ WHITESPACE@13..14 " "
+ EXPR_NUMBER@14..15
+ FLOAT@14..15 "1"
+ WHITESPACE@15..16 " "
+ FOR_SPEC@16..32
+ FOR_KW@16..19 "for"
+ WHITESPACE@19..20 " "
+ NAME@20..21
+ IDENT@20..21 "a"
+ WHITESPACE@21..22 " "
+ IN_KW@22..24 "in"
+ WHITESPACE@24..25 " "
+ EXPR_ARRAY@25..32
+ L_BRACK@25..26 "["
+ EXPR_NUMBER@26..27
+ FLOAT@26..27 "1"
+ COMMA@27..28 ","
+ EXPR_NUMBER@28..29
+ FLOAT@28..29 "2"
+ COMMA@29..30 ","
+ EXPR_NUMBER@30..31
+ FLOAT@30..31 "3"
+ R_BRACK@31..32 "]"
+ R_BRACE@32..33 "}"
+ WHITESPACE@33..34 "\n"
+===
+LocatedSyntaxError { error: Custom { error: "asserts can't be used in object comprehensions" }, range: 1..9 }
+===
+ x syntax error
+ ,----
+ 1 | {assert 1, a: 1 for a in [1,2,3]}
+ : ^^^^|^^^
+ : `-- asserts can't be used in object comprehensions
+ `----
crates/jrsonnet-rowan-parser/src/tests.rsdiffbeforeafterboth1#![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 // Prettier errors at EOF position59 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 "#120121122 no_rhs => r#"123 a +124 "#125 no_lhs => r#"126 + 2127 "#128 no_operator => "129 2 2130 "131132 named_before_positional => "133 a(1, 2, b=4, 3, 5, k = 12, 6)134 "135136 wrong_field_end => "137 {138 a: 1;139 b: 2;140 }141 "142143144 plain_call => "145 std.substr(a, 0, std.length(b)) == b146 "147148 destruct => "149 local [a, b, c] = arr;150 local [a, ...] = arr_rest;151 local [..., a] = rest_arr;152 local [...] = rest_in_arr;153 local [a, ...n] = arr_rest_n;154 local [...n, a] = rest_arr_n;155 local [...n] = rest_in_arr_n;156157 local {a, b, c} = obj;158 local {a, b, c, ...} = obj_rest;159 local {a, b, c, ...n} = obj_rest_n;160161 null162 "163164 str_block_missing_indent => "165 |||166 "167 str_block_missing_termination => "168 |||169 hello170 "171 str_block_missing_newline => "172 |||hello173 "174 str_block_missing_indent_text => "175 |||176 hello177 "178179 unexpected_destruct => "180 local * = 1;181 a182 "183 arr_compspec => r#"184 [a for a in [1, 2, 3]]185 "#186 arr_compspec_comma => "187 [a, for a in [1, 2, 3]]188 "189 arr_compspec_no_elems => "190 [for a in [1, 2, 3]]191 "192 arr_compspec_incompatible_with_multiple_elems => r#"193 [a for a in [1, 2, 3], b]194 "#195 arr_compspec_incompatible_with_multiple_elems_w => r#"196 [a, b, for a in [1, 2, 3], c]197 "#198199 obj_compspec => r#"200 {a:1 for a in [1, 2, 3]}201 "#202 obj_compspec_comma => "203 {a:1, for a in [1, 2, 3]}204 "205 obj_compspec_no_elems => "206 {for a in [1, 2, 3]}207 "208 obj_compspec_incompatible_with_multiple_elems => r#"209 {a:1 for a in [1, 2, 3], b:1}210 "#211 obj_compspec_incompatible_with_multiple_elems_w => r#"212 {a:1, b:1, for a in [1, 2, 3], c:1}213 "#214215 local_method => r#"216 local217 a(x) = x,218 a = function(x) x,219 ; c220 "#221 obj_method => r#"222 {223 a(x): x,224 a: function(x) x,225 }226 "#227);228229#[test]230fn stdlib() {231 let src = include_str!("../../jrsonnet-stdlib/src/std.jsonnet");232 let result = process(src);233 insta::assert_snapshot!("stdlib", result, src);234}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 // Prettier errors at EOF position59 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 "#120121122 no_rhs => r#"123 a +124 "#125 no_lhs => r#"126 + 2127 "#128 no_operator => "129 2 2130 "131132 named_before_positional => "133 a(1, 2, b=4, 3, 5, k = 12, 6)134 "135136 wrong_field_end => "137 {138 a: 1;139 b: 2;140 }141 "142143144 plain_call => "145 std.substr(a, 0, std.length(b)) == b146 "147148 destruct => "149 local [a, b, c] = arr;150 local [a, ...] = arr_rest;151 local [..., a] = rest_arr;152 local [...] = rest_in_arr;153 local [a, ...n] = arr_rest_n;154 local [...n, a] = rest_arr_n;155 local [...n] = rest_in_arr_n;156157 local {a, b, c} = obj;158 local {a, b, c, ...} = obj_rest;159 local {a, b, c, ...n} = obj_rest_n;160161 null162 "163164 str_block_missing_indent => "165 |||166 "167 str_block_missing_termination => "168 |||169 hello170 "171 str_block_missing_newline => "172 |||hello173 "174 str_block_missing_indent_text => "175 |||176 hello177 "178179 unexpected_destruct => "180 local * = 1;181 a182 "183 arr_compspec => r#"184 [a for a in [1, 2, 3]]185 "#186 arr_compspec_comma => "187 [a, for a in [1, 2, 3]]188 "189 arr_compspec_no_elems => "190 [for a in [1, 2, 3]]191 "192 arr_compspec_incompatible_with_multiple_elems => r#"193 [a for a in [1, 2, 3], b]194 "#195 arr_compspec_incompatible_with_multiple_elems_w => r#"196 [a, b, for a in [1, 2, 3], c]197 "#198199 obj_compspec => r#"200 {a:1 for a in [1, 2, 3]}201 "#202 obj_compspec_comma => "203 {a:1, for a in [1, 2, 3]}204 "205 obj_compspec_no_elems => "206 {for a in [1, 2, 3]}207 "208 obj_compspec_incompatible_with_multiple_elems => r#"209 {a:1 for a in [1, 2, 3], b:1}210 "#211 obj_compspec_incompatible_with_multiple_elems_w => r#"212 {a:1, b:1, for a in [1, 2, 3], c:1}213 "#214215 obj_compspec_incompatible_with_asserts => r#"216 {assert 1, a: 1 for a in [1,2,3]}217 "#218219 local_method => r#"220 local221 a(x) = x,222 a = function(x) x,223 ; c224 "#225 obj_method => r#"226 {227 a(x): x,228 a: function(x) x,229 }230 "#231);232233#[test]234fn stdlib() {235 let src = include_str!("../../jrsonnet-stdlib/src/std.jsonnet");236 let result = process(src);237 insta::assert_snapshot!("stdlib", result, src);238}