mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
ef6e0cca39
closes #9607
167 lines
4.7 KiB
Rust
167 lines
4.7 KiB
Rust
//! Conformance tests.
|
|
//!
|
|
//! Since these cases are a contract-as-code, they _must be well documented_. When adding a new
|
|
//! test, please describe what behavior it guarantees in as plain language as possible.
|
|
|
|
use std::{borrow::Cow, sync::Arc};
|
|
|
|
use oxc_diagnostics::{GraphicalReportHandler, GraphicalTheme, NamedSource, OxcDiagnostic};
|
|
use oxc_semantic::{AstNode, Semantic, SymbolId};
|
|
|
|
use crate::TestContext;
|
|
|
|
mod test_identifier_reference;
|
|
mod test_symbol_declaration;
|
|
|
|
pub fn conformance_suite() -> SemanticConformance {
|
|
SemanticConformance::default()
|
|
.with_test(test_symbol_declaration::SymbolDeclarationTest)
|
|
.with_test(test_identifier_reference::IdentifierReferenceTest)
|
|
}
|
|
|
|
pub trait ConformanceTest {
|
|
fn name(&self) -> &'static str;
|
|
|
|
#[must_use]
|
|
#[expect(unused_variables)]
|
|
fn run_once(&self, semantic: &Semantic<'_>) -> TestResult {
|
|
TestResult::Pass
|
|
}
|
|
|
|
#[must_use]
|
|
#[expect(unused_variables)]
|
|
fn run_on_node<'a>(&self, node: &AstNode<'a>, semantic: &Semantic<'a>) -> TestResult {
|
|
TestResult::Pass
|
|
}
|
|
|
|
#[must_use]
|
|
#[expect(unused_variables)]
|
|
fn run_on_symbol(&self, symbol_id: SymbolId, semantic: &Semantic<'_>) -> TestResult {
|
|
TestResult::Pass
|
|
}
|
|
}
|
|
|
|
pub struct SemanticConformance {
|
|
tests: Vec<Box<dyn ConformanceTest>>,
|
|
reporter: GraphicalReportHandler,
|
|
}
|
|
|
|
impl Default for SemanticConformance {
|
|
fn default() -> Self {
|
|
Self {
|
|
tests: Vec::new(),
|
|
reporter: GraphicalReportHandler::default()
|
|
.with_theme(GraphicalTheme::unicode_nocolor()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SemanticConformance {
|
|
/// Add a test case to the conformance suite.
|
|
pub fn with_test<Test: ConformanceTest + 'static>(mut self, test: Test) -> Self {
|
|
self.tests.push(Box::new(test));
|
|
self
|
|
}
|
|
|
|
pub fn run_on_source(&self, ctx: &TestContext<'_>) -> String {
|
|
let named_source = Arc::new(NamedSource::new(
|
|
ctx.path.to_string_lossy(),
|
|
ctx.semantic.source_text().to_string(),
|
|
));
|
|
|
|
let results = self
|
|
.run(&ctx.semantic)
|
|
.into_iter()
|
|
.map(|diagnostic| diagnostic.with_source_code(Arc::clone(&named_source)))
|
|
.collect::<Vec<_>>();
|
|
|
|
if results.is_empty() {
|
|
return String::new();
|
|
}
|
|
|
|
let mut output = String::new();
|
|
for result in results {
|
|
self.reporter.render_report(&mut output, result.as_ref()).unwrap();
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
fn run(&self, semantic: &Semantic) -> Vec<OxcDiagnostic> {
|
|
let mut diagnostics = Vec::new();
|
|
for test in &self.tests {
|
|
// Run file-level tests
|
|
self.record_results(&mut diagnostics, test.as_ref(), test.run_once(semantic));
|
|
|
|
// Run AST node tests
|
|
for node in semantic.nodes() {
|
|
self.record_results(
|
|
&mut diagnostics,
|
|
test.as_ref(),
|
|
test.run_on_node(node, semantic),
|
|
);
|
|
}
|
|
|
|
// Run symbol tests
|
|
for symbol_id in semantic.scoping().symbol_ids() {
|
|
self.record_results(
|
|
&mut diagnostics,
|
|
test.as_ref(),
|
|
test.run_on_symbol(symbol_id, semantic),
|
|
);
|
|
}
|
|
}
|
|
|
|
diagnostics
|
|
}
|
|
|
|
#[expect(clippy::unused_self)]
|
|
fn record_results(
|
|
&self,
|
|
diagnostics: &mut Vec<OxcDiagnostic>,
|
|
test: &dyn ConformanceTest,
|
|
result: TestResult,
|
|
) {
|
|
if let TestResult::Fail(reasons) = result {
|
|
diagnostics.extend(
|
|
reasons.into_iter().map(|reason| reason.with_error_code_scope(test.name())),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum TestResult {
|
|
Pass,
|
|
Fail(/* reasons */ Vec<OxcDiagnostic>),
|
|
}
|
|
impl From<String> for TestResult {
|
|
fn from(reason: String) -> Self {
|
|
TestResult::Fail(vec![OxcDiagnostic::error(Cow::Owned(reason))])
|
|
}
|
|
}
|
|
impl From<Option<String>> for TestResult {
|
|
fn from(result: Option<String>) -> Self {
|
|
match result {
|
|
Some(reason) => TestResult::Fail(vec![OxcDiagnostic::error(Cow::Owned(reason))]),
|
|
None => TestResult::Pass,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<OxcDiagnostic> for TestResult {
|
|
fn from(diagnostic: OxcDiagnostic) -> Self {
|
|
TestResult::Fail(vec![diagnostic])
|
|
}
|
|
}
|
|
impl From<Vec<OxcDiagnostic>> for TestResult {
|
|
fn from(diagnostics: Vec<OxcDiagnostic>) -> Self {
|
|
TestResult::Fail(diagnostics)
|
|
}
|
|
}
|
|
impl FromIterator<OxcDiagnostic> for TestResult {
|
|
fn from_iter<I: IntoIterator<Item = OxcDiagnostic>>(iter: I) -> Self {
|
|
TestResult::Fail(iter.into_iter().collect())
|
|
}
|
|
}
|