use std::fmt::Write as _;
use cyrs_syntax::{SyntaxNode, parse};
use rowan::{NodeOrToken, WalkEvent};
fn format_cst(node: &SyntaxNode) -> String {
let mut out = String::new();
let mut depth: usize = 0;
for ev in node.preorder_with_tokens() {
match ev {
WalkEvent::Enter(NodeOrToken::Node(n)) => {
for _ in 0..depth {
out.push_str(" ");
}
let r = n.text_range();
let start: u32 = r.start().into();
let end: u32 = r.end().into();
writeln!(out, "{:?}@{start}..{end}", n.kind()).unwrap();
depth += 1;
}
WalkEvent::Leave(NodeOrToken::Node(_)) => {
depth -= 1;
}
WalkEvent::Enter(NodeOrToken::Token(t)) => {
for _ in 0..depth {
out.push_str(" ");
}
let r = t.text_range();
let start: u32 = r.start().into();
let end: u32 = r.end().into();
writeln!(out, "{:?}@{start}..{end} {:?}", t.kind(), t.text()).unwrap();
}
WalkEvent::Leave(NodeOrToken::Token(_)) => {}
}
}
out
}
fn format_with_errors(src: &str) -> String {
let parse = parse(src);
let mut out = format_cst(&parse.syntax());
let errs = parse.errors();
if !errs.is_empty() {
out.push_str("---errors---\n");
for e in errs {
let off: u32 = e.offset.into();
writeln!(out, "@{off}: {}", e.message).unwrap();
}
}
out
}
#[test]
fn clause_match_basic() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n"));
}
#[test]
fn clause_match_labeled_node() {
insta::assert_snapshot!(format_with_errors("MATCH (p:Person) RETURN p"));
}
#[test]
fn clause_match_multiple_patterns() {
insta::assert_snapshot!(format_with_errors("MATCH (a), (b), (c) RETURN a, b, c"));
}
#[test]
fn clause_optional_match() {
insta::assert_snapshot!(format_with_errors(
"OPTIONAL MATCH (a:Person {name: 'Alice'}) RETURN a"
));
}
#[test]
fn clause_match_where() {
insta::assert_snapshot!(format_with_errors("MATCH (n) WHERE n.age > 18 RETURN n"));
}
#[test]
fn clause_return_basic() {
insta::assert_snapshot!(format_with_errors("RETURN 1"));
}
#[test]
fn clause_return_distinct() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN DISTINCT n.name"));
}
#[test]
fn clause_return_star() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN *"));
}
#[test]
fn clause_return_order_by_skip_limit() {
insta::assert_snapshot!(format_with_errors(
"MATCH (n) RETURN n.name ORDER BY n.age DESC SKIP 1 LIMIT 5"
));
}
#[test]
fn clause_return_alias() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n.name AS name"));
}
#[test]
fn clause_with_simple_projection() {
insta::assert_snapshot!(format_with_errors(
"MATCH (n:Person) WITH n.name AS name RETURN name"
));
}
#[test]
fn clause_with_where_filter() {
insta::assert_snapshot!(format_with_errors(
"MATCH (n) WITH n WHERE n.age > 21 RETURN n"
));
}
#[test]
fn clause_with_order_by_limit() {
insta::assert_snapshot!(format_with_errors(
"MATCH (n) WITH n ORDER BY n.name LIMIT 5 RETURN n"
));
}
#[test]
fn clause_unwind_list() {
insta::assert_snapshot!(format_with_errors("UNWIND [1, 2, 3] AS x RETURN x"));
}
#[test]
fn clause_create_node() {
insta::assert_snapshot!(format_with_errors("CREATE (n:Person {name: 'Alice'})"));
}
#[test]
fn clause_merge_node_on_create() {
insta::assert_snapshot!(format_with_errors(
"MERGE (n:Person {name: 'A'}) ON CREATE SET n.created = 1 ON MATCH SET n.seen = 1 RETURN n"
));
}
#[test]
fn clause_set_property() {
insta::assert_snapshot!(format_with_errors("MATCH (n:Person) SET n.active = true"));
}
#[test]
fn clause_set_label() {
insta::assert_snapshot!(format_with_errors("MATCH (n) SET n:Admin"));
}
#[test]
fn clause_remove_property() {
insta::assert_snapshot!(format_with_errors("MATCH (n:Person) REMOVE n.age"));
}
#[test]
fn clause_remove_label() {
insta::assert_snapshot!(format_with_errors("MATCH (n) REMOVE n:Admin"));
}
#[test]
fn clause_delete() {
insta::assert_snapshot!(format_with_errors("MATCH (n:Person) DELETE n"));
}
#[test]
fn clause_detach_delete() {
insta::assert_snapshot!(format_with_errors("MATCH (n:Person) DETACH DELETE n"));
}
#[test]
fn pattern_anonymous_node() {
insta::assert_snapshot!(format_with_errors("MATCH () RETURN 1"));
}
#[test]
fn pattern_bound_node() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n"));
}
#[test]
fn pattern_multi_label_node() {
insta::assert_snapshot!(format_with_errors("MATCH (n:Person:Employee) RETURN n"));
}
#[test]
fn pattern_node_with_properties() {
insta::assert_snapshot!(format_with_errors(
"MATCH (n:Person {name: 'Alice', age: 30}) RETURN n"
));
}
#[test]
fn pattern_rel_undirected() {
insta::assert_snapshot!(format_with_errors("MATCH (a)-[r]-(b) RETURN a, b"));
}
#[test]
fn pattern_rel_directed_right() {
insta::assert_snapshot!(format_with_errors("MATCH (a)-[:KNOWS]->(b) RETURN a, b"));
}
#[test]
fn pattern_rel_directed_left() {
insta::assert_snapshot!(format_with_errors("MATCH (a)<-[:KNOWS]-(b) RETURN a, b"));
}
#[test]
fn pattern_rel_with_props() {
insta::assert_snapshot!(format_with_errors(
"MATCH (a)-[r:KNOWS {since: 2020}]->(b) RETURN r"
));
}
#[test]
fn pattern_chain_three_nodes() {
insta::assert_snapshot!(format_with_errors(
"MATCH (a)-[:R1]->(b)-[:R2]->(c) RETURN a, b, c"
));
}
#[test]
fn pattern_var_length_range() {
insta::assert_snapshot!(format_with_errors(
"MATCH (a)-[:KNOWS*1..3]->(b) RETURN a, b"
));
}
#[test]
fn pattern_var_length_unbounded() {
insta::assert_snapshot!(format_with_errors("MATCH (a)-[r:KNOWS*]->(b) RETURN a"));
}
#[test]
fn pattern_named_path() {
insta::assert_snapshot!(format_with_errors("MATCH p = (a)-[:KNOWS]->(b) RETURN p"));
}
#[test]
fn pattern_shortest_path_named() {
insta::assert_snapshot!(format_with_errors(
"MATCH p = shortestPath((a)-[:KNOWS*]->(b)) RETURN p"
));
}
#[test]
fn pattern_all_shortest_paths_named() {
insta::assert_snapshot!(format_with_errors(
"MATCH p = allShortestPaths((a)-[:KNOWS*1..5]->(b)) RETURN p"
));
}
#[test]
fn pattern_shortest_path_anonymous() {
insta::assert_snapshot!(format_with_errors(
"MATCH shortestPath((a)-[*]->(b)) RETURN a, b"
));
}
#[test]
fn expr_literal_int() {
insta::assert_snapshot!(format_with_errors("RETURN 42"));
}
#[test]
fn expr_literal_float() {
insta::assert_snapshot!(format_with_errors("RETURN 3.14"));
}
#[test]
fn expr_literal_string() {
insta::assert_snapshot!(format_with_errors("RETURN 'hello'"));
}
#[test]
fn expr_literal_bool() {
insta::assert_snapshot!(format_with_errors("RETURN TRUE, FALSE"));
}
#[test]
fn expr_literal_null() {
insta::assert_snapshot!(format_with_errors("RETURN NULL"));
}
#[test]
fn expr_parameter() {
insta::assert_snapshot!(format_with_errors("RETURN $name"));
}
#[test]
fn expr_list_literal_ints() {
insta::assert_snapshot!(format_with_errors("RETURN [1, 2, 3]"));
}
#[test]
fn expr_list_literal_empty() {
insta::assert_snapshot!(format_with_errors("RETURN []"));
}
#[test]
fn expr_map_literal_basic() {
insta::assert_snapshot!(format_with_errors("RETURN {a: 1, b: 'two'}"));
}
#[test]
fn expr_map_projection_property_selectors() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n { .name, .age }"));
}
#[test]
fn expr_map_projection_literal_item() {
insta::assert_snapshot!(format_with_errors(
"MATCH (n) RETURN n { full_name: n.name, .age }"
));
}
#[test]
fn expr_map_projection_all_properties_spread() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n { .* }"));
}
#[test]
fn expr_map_projection_all_bound_vars_spread() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n { * }"));
}
#[test]
fn expr_map_projection_mixed_all_four_kinds() {
insta::assert_snapshot!(format_with_errors(
"MATCH (n) RETURN n { .name, age: 30, .*, * }"
));
}
#[test]
fn expr_map_projection_empty() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n { }"));
}
#[test]
fn expr_map_literal_remains_literal_without_subject() {
insta::assert_snapshot!(format_with_errors("RETURN { a: 1, b: 2 }"));
}
#[test]
fn err_map_projection_unclosed() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n { .name"));
}
#[test]
fn err_map_projection_missing_colon() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n { age 30 }"));
}
#[test]
fn err_map_projection_dot_then_garbage() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n { . , .age }"));
}
#[test]
fn expr_variable() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n"));
}
#[test]
fn expr_property_access() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN n.name"));
}
#[test]
fn expr_subscript() {
insta::assert_snapshot!(format_with_errors("RETURN a[0]"));
}
#[test]
fn expr_index_literal() {
insta::assert_snapshot!(format_with_errors("RETURN xs[0]"));
}
#[test]
fn expr_index_negative() {
insta::assert_snapshot!(format_with_errors("RETURN xs[-1]"));
}
#[test]
fn expr_slice_bounded() {
insta::assert_snapshot!(format_with_errors("RETURN xs[0..3]"));
}
#[test]
fn expr_slice_elided_start() {
insta::assert_snapshot!(format_with_errors("RETURN xs[..3]"));
}
#[test]
fn expr_slice_elided_end() {
insta::assert_snapshot!(format_with_errors("RETURN xs[0..]"));
}
#[test]
fn expr_slice_then_index_chain() {
insta::assert_snapshot!(format_with_errors("RETURN xs[0..3][0]"));
}
#[test]
fn expr_list_comprehension_identity() {
insta::assert_snapshot!(format_with_errors("RETURN [x IN [1, 2, 3]]"));
}
#[test]
fn expr_list_comprehension_filter_only() {
insta::assert_snapshot!(format_with_errors("RETURN [x IN [1, 2, 3] WHERE x > 1]"));
}
#[test]
fn expr_list_comprehension_map_only() {
insta::assert_snapshot!(format_with_errors("RETURN [x IN [1, 2, 3] | x + 1]"));
}
#[test]
fn expr_list_comprehension_filter_and_map() {
insta::assert_snapshot!(format_with_errors(
"RETURN [x IN [1, 2, 3] WHERE x > 1 | x + 1]"
));
}
#[test]
fn err_list_comprehension_unterminated_where() {
insta::assert_snapshot!(format_with_errors("RETURN [x IN xs WHERE x > 1"));
}
#[test]
fn expr_function_call() {
insta::assert_snapshot!(format_with_errors("RETURN count(n)"));
}
#[test]
fn expr_function_call_distinct() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN count(DISTINCT n)"));
}
#[test]
fn expr_paren() {
insta::assert_snapshot!(format_with_errors("RETURN (1 + 2) * 3"));
}
#[test]
fn expr_list_predicate_any_with_where() {
insta::assert_snapshot!(format_with_errors("RETURN ANY(x IN xs WHERE x > 0)"));
}
#[test]
fn expr_list_predicate_all_bare() {
insta::assert_snapshot!(format_with_errors("RETURN ALL(x IN xs)"));
}
#[test]
fn expr_list_predicate_none_prop_filter() {
insta::assert_snapshot!(format_with_errors(
"RETURN NONE(x IN xs WHERE x.foo = 'bar')"
));
}
#[test]
fn expr_list_predicate_single_with_where() {
insta::assert_snapshot!(format_with_errors("RETURN SINGLE(x IN xs WHERE x < 10)"));
}
#[test]
fn err_list_predicate_unterminated_where() {
insta::assert_snapshot!(format_with_errors("RETURN ANY(x IN xs WHERE"));
}
#[test]
fn expr_exists_pattern_simple() {
insta::assert_snapshot!(format_with_errors("RETURN EXISTS((a)-->(b))"));
}
#[test]
fn expr_exists_pattern_with_labels() {
insta::assert_snapshot!(format_with_errors(
"RETURN EXISTS((a:Person)-[:KNOWS]->(b:Person))"
));
}
#[test]
fn expr_exists_pattern_in_where() {
insta::assert_snapshot!(format_with_errors(
"MATCH (a) WHERE EXISTS((a)-->(:Movie)) RETURN a"
));
}
#[test]
fn expr_exists_function_call_form() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN exists(n.prop)"));
}
#[test]
fn err_exists_block_deferred() {
insta::assert_snapshot!(format_with_errors(
"MATCH (a) WHERE EXISTS { MATCH (a) RETURN a } RETURN a"
));
}
#[test]
fn expr_bare_pattern_predicate_in_where() {
insta::assert_snapshot!(format_with_errors(
"MATCH (a) WHERE (a)-->(:Movie) RETURN a"
));
}
#[test]
fn expr_bare_pattern_predicate_not_negation() {
insta::assert_snapshot!(format_with_errors(
"MATCH (a) WHERE NOT (a)-->(:Villain) RETURN a"
));
}
#[test]
fn expr_bare_pattern_predicate_single_var_ambiguous() {
insta::assert_snapshot!(format_with_errors("MATCH (n) WHERE (n) RETURN n"));
}
#[test]
fn expr_bare_pattern_predicate_empty_node() {
insta::assert_snapshot!(format_with_errors("MATCH (a) WHERE ()-->() RETURN a"));
}
#[test]
fn expr_bare_pattern_predicate_reverse_direction() {
insta::assert_snapshot!(format_with_errors("MATCH (n) WHERE (n)<-[]-() RETURN n"));
}
#[test]
fn expr_paren_still_parens_arithmetic() {
insta::assert_snapshot!(format_with_errors("RETURN (1 + 2)"));
}
#[test]
fn expr_paren_still_parens_property_access() {
insta::assert_snapshot!(format_with_errors("MATCH (a) RETURN (a.name)"));
}
#[test]
fn expr_binary_arithmetic() {
insta::assert_snapshot!(format_with_errors("RETURN 1 + 2 * 3"));
}
#[test]
fn expr_binary_power_right_assoc() {
insta::assert_snapshot!(format_with_errors("RETURN 2 ^ 3 ^ 2"));
}
#[test]
fn expr_binary_comparison() {
insta::assert_snapshot!(format_with_errors("MATCH (n) WHERE n.age >= 18 RETURN n"));
}
#[test]
fn expr_binary_boolean() {
insta::assert_snapshot!(format_with_errors("RETURN NOT a OR b AND c"));
}
#[test]
fn expr_starts_with() {
insta::assert_snapshot!(format_with_errors("RETURN a STARTS WITH 'foo'"));
}
#[test]
fn expr_ends_with() {
insta::assert_snapshot!(format_with_errors("RETURN a ENDS WITH 'foo'"));
}
#[test]
fn expr_contains() {
insta::assert_snapshot!(format_with_errors("RETURN a CONTAINS 'foo'"));
}
#[test]
fn expr_regex_match() {
insta::assert_snapshot!(format_with_errors("RETURN a =~ 'r.*'"));
}
#[test]
fn expr_unary_minus() {
insta::assert_snapshot!(format_with_errors("RETURN -1"));
}
#[test]
fn expr_unary_not() {
insta::assert_snapshot!(format_with_errors("RETURN NOT a"));
}
#[test]
fn expr_is_null() {
insta::assert_snapshot!(format_with_errors("RETURN a IS NULL"));
}
#[test]
fn expr_is_not_null() {
insta::assert_snapshot!(format_with_errors("RETURN a IS NOT NULL"));
}
#[test]
fn expr_in_operator() {
insta::assert_snapshot!(format_with_errors(
"MATCH (n) WHERE n.age IN n.ages RETURN n"
));
}
#[test]
fn multi_stmt_empty_file() {
insta::assert_snapshot!(format_with_errors(""));
}
#[test]
fn multi_stmt_single_no_semi() {
insta::assert_snapshot!(format_with_errors("RETURN 1"));
}
#[test]
fn multi_stmt_single_trailing_semi() {
insta::assert_snapshot!(format_with_errors("RETURN 1;"));
}
#[test]
fn multi_stmt_two_statements() {
insta::assert_snapshot!(format_with_errors("RETURN 1; RETURN 2"));
}
#[test]
fn multi_stmt_two_with_trailing_semi() {
insta::assert_snapshot!(format_with_errors(
"MATCH (n) RETURN n; MATCH (m) RETURN m;"
));
}
#[test]
fn err_unclosed_paren_in_pattern() {
insta::assert_snapshot!(format_with_errors("MATCH (n RETURN n"));
}
#[test]
fn err_missing_return_target() {
insta::assert_snapshot!(format_with_errors("MATCH (n) RETURN"));
}
#[test]
fn err_stray_keyword_after_where() {
insta::assert_snapshot!(format_with_errors("MATCH (n) WHERE RETURN n"));
}
#[test]
fn err_leading_garbage() {
insta::assert_snapshot!(format_with_errors("garbage MATCH (n) RETURN n"));
}
#[test]
fn err_unclosed_property_map() {
insta::assert_snapshot!(format_with_errors("MATCH (n {x: 1 RETURN n"));
}
#[test]
fn err_missing_label_after_colon() {
insta::assert_snapshot!(format_with_errors("MATCH (n:) RETURN n"));
}
#[test]
fn err_unterminated_string() {
insta::assert_snapshot!(format_with_errors("RETURN 'oops"));
}
#[test]
fn err_missing_closing_bracket_in_rel() {
insta::assert_snapshot!(format_with_errors("MATCH (a)-[r (b) RETURN a"));
}