use core::mem::size_of;
use ox_mf2_parser::{
parse_message, parse_source, parse_source_session, CstChild, CstNodeView, CstView,
DiagnosticCode, ParseCapacity, ParseOptions, ParseWorkspace, SourceFileInput, SourceStore,
SyntaxKind,
};
#[test]
fn syntax_kind_stays_a_u16() {
assert_eq!(size_of::<SyntaxKind>(), 2);
}
#[test]
fn span_stays_eight_bytes() {
assert_eq!(size_of::<ox_mf2_parser::Span>(), 8);
}
#[test]
fn valid_input_does_not_emit_diagnostics() {
for case in [
"",
"Hello",
"Hello, {$name}!",
"{:datetime}",
"{{Hello, world!}}",
".input {$x}\n{{Hi {$x}}}",
".match $x\n* {{fallback}}",
] {
let result = parse_message(case);
assert!(
result.diagnostics.is_empty(),
"case `{case}` produced diagnostics: {:?}",
result.diagnostics
);
}
}
#[test]
fn workspace_reuse_does_not_regrow_capacity() {
let input = "Hello, {$name}!";
let mut sources = SourceStore::new();
let id = sources.add(SourceFileInput {
source: input,
..Default::default()
});
let mut workspace = ParseWorkspace::with_capacity(ParseCapacity::new(32, 32, 32, 32, 0));
workspace.reserve_for_source_len(input.len() * 4);
for _ in 0..4 {
let _ = parse_source_session(&sources, id, &mut workspace, ParseOptions::default());
}
let cap_after_warmup = workspace.node_capacity();
let pending_cap_after_warmup = workspace.pending_edges_capacity();
let frame_cap_after_warmup = workspace.frame_starts_capacity();
for _ in 0..1024 {
let _ = parse_source_session(&sources, id, &mut workspace, ParseOptions::default());
}
assert_eq!(workspace.node_capacity(), cap_after_warmup);
assert_eq!(workspace.pending_edges_capacity(), pending_cap_after_warmup);
assert_eq!(workspace.frame_starts_capacity(), frame_cap_after_warmup);
}
#[test]
fn trivia_runs_collapse_contiguous_whitespace() {
let result = parse_message(".local $x = {$y}\n{{Hi}}");
assert_eq!(
result.cst.trivia_count(),
4,
"expected one trivia record per ws run, got {}",
result.cst.trivia_count(),
);
}
#[test]
fn cst_view_traversal_visits_every_node_and_token() {
let result = parse_message("Hello, {$name}!");
let sources = SourceStore::new();
let view = CstView::new(&sources, result.source, &result.cst);
let mut visited_nodes = 0usize;
let mut visited_tokens = 0usize;
if let Some(root) = view.root() {
walk(root, &mut visited_nodes, &mut visited_tokens);
}
assert_eq!(visited_nodes, result.cst.node_count());
assert_eq!(visited_tokens, result.cst.token_count());
}
fn walk(node: CstNodeView<'_>, nodes: &mut usize, tokens: &mut usize) {
*nodes += 1;
for child in node.children() {
match child {
CstChild::Node(n) => walk(n, nodes, tokens),
CstChild::Token(_) => *tokens += 1,
}
}
}
#[test]
fn recovery_does_not_cascade() {
for (input, expected_code, max_diagnostics) in [
("Hello, {$name", DiagnosticCode::UnclosedExpression, 1),
("{{unterminated", DiagnosticCode::UnclosedQuotedPattern, 1),
("{|broken}", DiagnosticCode::UnclosedQuotedLiteral, 2),
] {
let mut sources = SourceStore::new();
let id = sources.add(SourceFileInput {
source: input,
..Default::default()
});
let result = parse_source(&sources, id, ParseOptions::default());
assert!(
result.diagnostics.len() <= max_diagnostics,
"case `{input}` cascaded: {:?}",
result.diagnostics
);
assert!(
result.diagnostics.iter().any(|d| d.code == expected_code),
"case `{input}` did not emit {expected_code:?}: {:?}",
result.diagnostics
);
}
}