rott/rottlib/tests/parser_diagnostics/mod.rs

128 lines
3.5 KiB
Rust

use std::collections::HashMap;
use rottlib::arena::Arena;
use rottlib::diagnostics::{Diagnostic, Severity};
use rottlib::lexer::{TokenPosition, TokenSpan, TokenizedFile};
use rottlib::parser::Parser;
mod block_items;
mod control_flow_expressions;
mod primary_expressions;
mod selector_expressions;
#[derive(Debug)]
pub(super) struct ExpectedLabel {
pub span: TokenSpan,
pub message: &'static str,
}
#[derive(Debug)]
pub(super) struct ExpectedDiagnostic<'a> {
pub headline: &'static str,
pub severity: Severity,
pub code: Option<&'static str>,
pub primary_label: Option<ExpectedLabel>,
pub secondary_labels: &'a [ExpectedLabel],
pub help: Option<&'static str>,
pub notes: &'a [&'static str],
}
#[track_caller]
pub(super) fn assert_diagnostic(actual: &Diagnostic, expected: &ExpectedDiagnostic<'_>) {
assert_eq!(actual.headline(), expected.headline);
assert_eq!(actual.severity(), expected.severity);
assert_eq!(actual.code(), expected.code);
assert_eq!(actual.help(), expected.help);
match (actual.primary_label(), expected.primary_label.as_ref()) {
(None, None) => {}
(Some(actual), Some(expected)) => {
assert_eq!(actual.span, expected.span);
assert_eq!(actual.message, expected.message);
}
_ => panic!("primary label mismatch"),
}
let actual_secondary = actual.secondary_labels();
assert_eq!(actual_secondary.len(), expected.secondary_labels.len());
for (actual, expected) in actual_secondary
.iter()
.zip(expected.secondary_labels.iter())
{
assert_eq!(actual.span, expected.span);
assert_eq!(actual.message, expected.message);
}
let actual_notes = actual.notes();
assert_eq!(actual_notes.len(), expected.notes.len());
for (actual, expected) in actual_notes.iter().zip(expected.notes.iter()) {
assert_eq!(actual, expected);
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct Fixture {
pub label: &'static str,
pub source: &'static str,
}
pub(super) type FixtureRun = Vec<Diagnostic>;
pub(super) struct FixtureRuns {
runs: HashMap<&'static str, FixtureRun>,
}
impl FixtureRuns {
#[track_caller]
pub fn get(&self, label: &str) -> Option<Vec<Diagnostic>> {
self.runs.get(label).map(|fixture_run| fixture_run.clone())
}
#[track_caller]
pub fn get_any(&self, label: &str) -> Diagnostic {
self.runs
.get(label)
.map(|fixture_run| fixture_run[0].clone())
.unwrap()
}
#[track_caller]
pub fn get_by_code(&self, label: &str, code: &str) -> Diagnostic {
self.runs
.get(label)
.unwrap_or_else(|| panic!("no fixture run for `{label}`"))
.iter()
.find(|diagnostic| diagnostic.code().as_deref() == Some(code))
.unwrap_or_else(|| panic!("no `{code}` diagnostic in fixture `{label}`"))
.clone()
}
}
fn run_fixture(fixture: &'static Fixture) -> FixtureRun {
let arena = Arena::new();
let file = TokenizedFile::tokenize(fixture.source);
let mut parser = Parser::new(&file, &arena);
let _ = parser.parse_expression();
let diagnostics = parser.diagnostics.clone();
for diagnostic in &diagnostics {
diagnostic.render(&file, fixture.label);
println!();
}
diagnostics
}
pub(super) fn run_fixtures(fixtures: &'static [Fixture]) -> FixtureRuns {
let mut runs = HashMap::new();
for fixture in fixtures {
runs.insert(fixture.label, run_fixture(fixture));
}
FixtureRuns { runs }
}