css_parse/
test_helpers.rs

1/// (Requires feature "testing") Given a Node, and a string, this will expand to code that sets up a parser, and parses the given string against the
2/// given node. If the parse failed this macro will [panic] with a readable failure. It then writes the result out using
3/// [crate::CursorWriteSink], writing the parsed Node back out to a string. If resulting string from the given string, then the
4/// macro will [panic] with a readable failure.
5///
6/// In rare cases it might be necessary to ensure the resulting string _differs_ from the input, for example if a
7/// grammar is serialized in a specific order but allows parsing in any order (many style values do this). In these
8/// cases, a second string can be provided which will be asserted gainst the output instead.
9///
10/// ```
11/// use css_parse::*;
12/// assert_parse!(T![Ident], "foo");
13/// // Equivalent to:
14/// assert_parse!(T![Ident], "foo", "foo");
15/// ```
16///
17/// For more complex types (for example enum variants), you might want to assert that the given AST
18/// node matches an expected pattern (for example, one enum variant was chosen over another). In
19/// these cases, passing the match pattern as the third (or fourth) argument will assert that the
20/// parsed output struct matches the given pattern:
21///
22/// ```
23/// use css_parse::*;
24/// use csskit_derives::*;
25/// #[derive(Parse, ToCursors, Debug)]
26/// enum IdentOrNumber {
27///     Ident(T![Ident]),
28///     Number(T![Number]),
29/// }
30/// assert_parse!(IdentOrNumber, "foo", IdentOrNumber::Ident(_));
31/// assert_parse!(IdentOrNumber, "12", IdentOrNumber::Number(_));
32/// ```
33#[macro_export]
34macro_rules! assert_parse {
35	($ty: ty, $str: literal, $str2: literal, $($ast: pat)+) => {
36		let source_text = $str;
37		let expected = $str2;
38		let bump = ::bumpalo::Bump::default();
39		let mut parser = $crate::Parser::new(&bump, &source_text);
40		let result = parser.parse_entirely::<$ty>();
41		if !result.errors.is_empty() {
42			panic!("\n\nParse failed. ({:?}) saw error {:?}", source_text, result.errors[0]);
43		}
44		let mut actual = ::bumpalo::collections::String::new_in(&bump);
45		let mut cursors = $crate::CursorWriteSink::new(&source_text, &mut actual);
46		{
47			use $crate::ToCursors;
48			result.to_cursors(&mut cursors);
49		}
50		if expected != actual {
51			panic!("\n\nParse failed: did not match expected format:\n\n   parser input: {:?}\n  parser output: {:?}\n       expected: {:?}\n", source_text, actual, expected);
52		}
53		#[allow(clippy::redundant_pattern_matching)] // Avoid .clone().unwrap()
54		if !matches!(result.output, Some($($ast)|+)) {
55			panic!(
56        "\n\nParse succeeded but struct did not match given match pattern:\n\n           input: {:?}\n  match pattern: {}\n  parsed struct: {:#?}\n",
57        source_text,
58        stringify!($($ast)|+),
59        result.output.unwrap(),
60      );
61    }
62	};
63	($ty: ty, $str: literal) => {
64		assert_parse!($ty, $str, $str, _);
65	};
66	($ty: ty, $str: literal, $str2: literal) => {
67		assert_parse!($ty, $str, $str2, _);
68	};
69	($ty: ty, $str: literal, $($ast: pat)+) => {
70		assert_parse!($ty, $str, $str, $($ast)+);
71	};
72}
73#[cfg(test)]
74pub(crate) use assert_parse;
75
76/// (Requires feature "testing") Given a Node, and a string, this will expand to code that sets up a parser, and parses the given string against the
77/// given node. If the parse succeeded this macro will [panic] with a readable failure.
78///
79/// In rare cases it might be necessary to ensure the resulting string _differs_ from the input, for example if a
80/// grammar is serialized in a specific order but allows parsing in any order (many style values do this). In these
81/// cases, a second string can be provided which will be asserted gainst the output instead.
82///
83/// ```
84/// use css_parse::*;
85/// assert_parse_error!(T![Ident], "1");
86/// ```
87#[macro_export]
88macro_rules! assert_parse_error {
89	($ty: ty, $str: literal) => {
90		let source_text = $str;
91		let bump = ::bumpalo::Bump::default();
92		let mut parser = $crate::Parser::new(&bump, source_text);
93		let result = parser.parse::<$ty>();
94		if parser.at_end() {
95			if let Ok(result) = result {
96				let mut actual = ::bumpalo::collections::String::new_in(&bump);
97				let mut cursors = $crate::CursorWriteSink::new(&source_text, &mut actual);
98				use $crate::ToCursors;
99				result.to_cursors(&mut cursors);
100				panic!("\n\nExpected errors but it passed without error.\n\n   parser input: {:?}\n  parser output: {:?}\n       expected: (Error)", source_text, actual);
101			}
102		}
103	};
104}
105#[cfg(test)]
106pub(crate) use assert_parse_error;
107
108/// (Requires feature "testing") Given a Node, and a multiline string, this will expand to code that sets up a parser,
109/// and parses the first line of the given string with the parser. It will then create a second string based on the span
110/// data and append it to the first line of the string, showing what was parsed and where the span rests.
111///
112/// This uses `parse`, as it will be often used in situations where there may be trailing unparsed tokens.
113///
114/// ```
115/// use css_parse::*;
116/// assert_parse_span!(T![Ident], r#"
117///     an_ident another_ident
118///     ^^^^^^^^
119/// "#);
120/// ```
121#[macro_export]
122macro_rules! assert_parse_span {
123	($ty: ty, $str: literal) => {
124		let expected = $str;
125		let source_text = expected.lines().find(|line| !line.trim().is_empty()).unwrap_or("");
126		let bump = ::bumpalo::Bump::default();
127		let mut parser = $crate::Parser::new(&bump, source_text);
128		let result = parser.parse::<$ty>();
129		match result {
130			Ok(result) => {
131				use $crate::ToSpan;
132				let span = result.to_span();
133				let indent = &source_text[0..span.start().into()];
134				if indent.trim().len() > 0 {
135					panic!(
136						"\n\nParse on {}:{} succeeded but has non-whitespace leading text: {}\n",
137						file!(),
138						line!(),
139						indent
140					);
141				}
142				let actual = format!("\n{}{}\n{}{}\n", indent, source_text, indent, "^".repeat(span.len() as usize));
143				if expected.trim() != actual.trim() {
144					panic!(
145						"\n\nParse on {}:{} succeeded but span ({}) differs:\n\n  expected: {}\n  actual: {}\n",
146						file!(),
147						line!(),
148						span,
149						expected,
150						actual,
151					);
152				}
153			}
154			Err(err) => {
155				panic!("\n\nParse on {}:{} failed. ({:?}) saw error {:?}", file!(), line!(), source_text, err);
156			}
157		}
158	};
159}
160#[cfg(test)]
161pub(crate) use assert_parse_span;