mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 20:32:10 +00:00
test(semantic): add scoping test cases (#954)
Add test cases to `oxc_semantic` that check scope flag behavior. Also contains these tweaks: - fix: allow disabling `with_module` on `SourceType` - refactor: move `SymbolTester` to a separate file - chore: add `Expect` trait & implement it on `SymbolTester`
This commit is contained in:
parent
9fcb3ce725
commit
b4b39b8aa6
6 changed files with 365 additions and 159 deletions
92
crates/oxc_semantic/tests/scopes.rs
Normal file
92
crates/oxc_semantic/tests/scopes.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use oxc_semantic::ScopeFlags;
|
||||||
|
use util::{Expect, SemanticTester};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_top_level_strict() {
|
||||||
|
// Module with top-level "use strict"
|
||||||
|
SemanticTester::js(
|
||||||
|
r#"
|
||||||
|
"use strict";
|
||||||
|
function foo() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.has_root_symbol("foo")
|
||||||
|
.is_in_scope(ScopeFlags::Top | ScopeFlags::StrictMode)
|
||||||
|
// .expect(expect_strict)
|
||||||
|
.test();
|
||||||
|
|
||||||
|
// Module without top-level "use strict"
|
||||||
|
SemanticTester::js(
|
||||||
|
r#"
|
||||||
|
function foo() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.has_root_symbol("foo")
|
||||||
|
.is_in_scope(ScopeFlags::Top | ScopeFlags::StrictMode)
|
||||||
|
.test();
|
||||||
|
|
||||||
|
// Script with top-level "use strict"
|
||||||
|
SemanticTester::js(
|
||||||
|
r#"
|
||||||
|
"use strict";
|
||||||
|
function foo() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.with_module(false)
|
||||||
|
.has_root_symbol("foo")
|
||||||
|
.is_in_scope(ScopeFlags::Top | ScopeFlags::StrictMode)
|
||||||
|
.test();
|
||||||
|
|
||||||
|
// Script without top-level "use strict"
|
||||||
|
SemanticTester::js(
|
||||||
|
r#"
|
||||||
|
function foo() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.with_module(false)
|
||||||
|
.has_root_symbol("foo")
|
||||||
|
.is_in_scope(ScopeFlags::Top)
|
||||||
|
.is_not_in_scope(ScopeFlags::StrictMode)
|
||||||
|
.test();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_function_level_strict() {
|
||||||
|
let tester = SemanticTester::js(
|
||||||
|
r#"
|
||||||
|
function foo() {
|
||||||
|
"use strict";
|
||||||
|
let x = 1;
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.with_module(false);
|
||||||
|
|
||||||
|
tester.has_some_symbol("x")
|
||||||
|
.is_in_scope(ScopeFlags::StrictMode | ScopeFlags::Function)
|
||||||
|
.expect(|(semantic, symbol_id)| -> Result<(), &'static str> {
|
||||||
|
let scope_id = semantic.symbol_scope(symbol_id);
|
||||||
|
let Some(parent_scope_id) = semantic.scopes().get_parent_id(scope_id) else {
|
||||||
|
return Err("Expected x's scope to have a parent")
|
||||||
|
};
|
||||||
|
let parent_flags = semantic.scopes().get_flags(parent_scope_id);
|
||||||
|
if parent_flags.contains(ScopeFlags::Top) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Expected x to be in a top-level function declaration, but its parent scope has flags {parent_flags:?}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.test();
|
||||||
|
tester.has_some_symbol("foo").is_not_in_scope(ScopeFlags::StrictMode).test();
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use oxc_semantic::SymbolFlags;
|
use oxc_semantic::SymbolFlags;
|
||||||
use util::SemanticTester;
|
use util::SemanticTester;
|
||||||
|
|
||||||
|
|
|
||||||
5
crates/oxc_semantic/tests/util/expect.rs
Normal file
5
crates/oxc_semantic/tests/util/expect.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub trait Expect<P, R> {
|
||||||
|
fn expect<F>(self, expectation: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnOnce(P) -> R;
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
|
mod expect;
|
||||||
|
mod symbol_tester;
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
use oxc_diagnostics::{
|
use oxc_diagnostics::{miette::NamedSource, Error};
|
||||||
miette::{miette, NamedSource},
|
|
||||||
Error,
|
|
||||||
};
|
|
||||||
extern crate miette;
|
extern crate miette;
|
||||||
use oxc_semantic::{Reference, Semantic, SemanticBuilder, SymbolFlags, SymbolId};
|
use oxc_semantic::{Semantic, SemanticBuilder};
|
||||||
use oxc_span::{Atom, SourceType};
|
use oxc_span::SourceType;
|
||||||
|
|
||||||
|
pub use expect::Expect;
|
||||||
|
pub use symbol_tester::SymbolTester;
|
||||||
|
|
||||||
pub struct SemanticTester {
|
pub struct SemanticTester {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
@ -50,6 +52,12 @@ impl SemanticTester {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn with_module(mut self, yes: bool) -> Self {
|
||||||
|
self.source_type = self.source_type.with_module(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse the source text and produce a new [`Semantic`]
|
/// Parse the source text and produce a new [`Semantic`]
|
||||||
#[allow(unstable_name_collisions)]
|
#[allow(unstable_name_collisions)]
|
||||||
pub fn build(&self) -> Semantic<'_> {
|
pub fn build(&self) -> Semantic<'_> {
|
||||||
|
|
@ -126,156 +134,3 @@ impl SemanticTester {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SymbolTester<'a> {
|
|
||||||
parent: &'a SemanticTester,
|
|
||||||
/// Reference to semantic analysis results, from [`SemanticTester`]
|
|
||||||
semantic: Semantic<'a>,
|
|
||||||
/// Name of the subject symbol
|
|
||||||
target_symbol_name: String,
|
|
||||||
/// Symbol data, or error if not found
|
|
||||||
test_result: Result<SymbolId, oxc_diagnostics::Error>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> SymbolTester<'a> {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(super) fn new_at_root(
|
|
||||||
parent: &'a SemanticTester,
|
|
||||||
semantic: Semantic<'a>,
|
|
||||||
target: &str,
|
|
||||||
) -> Self {
|
|
||||||
let decl =
|
|
||||||
semantic.scopes().get_binding(semantic.scopes().root_scope_id(), &Atom::from(target));
|
|
||||||
let data = decl.map_or_else(|| Err(miette!("Could not find declaration for {target}")), Ok);
|
|
||||||
|
|
||||||
SymbolTester { parent, semantic, target_symbol_name: target.to_string(), test_result: data }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn new_unique(
|
|
||||||
parent: &'a SemanticTester,
|
|
||||||
semantic: Semantic<'a>,
|
|
||||||
target: &str,
|
|
||||||
) -> Self {
|
|
||||||
let symbols_with_target_name: Vec<_> =
|
|
||||||
semantic.scopes().iter_bindings().filter(|(_, _, name)| name == &target).collect();
|
|
||||||
let data = match symbols_with_target_name.len() {
|
|
||||||
0 => Err(miette!("Could not find declaration for {target}")),
|
|
||||||
1 => Ok(symbols_with_target_name.iter().map(|(_, symbol_id, _)| *symbol_id).next().unwrap()),
|
|
||||||
n if n > 1 => Err(miette!("Couldn't uniquely resolve symbol id for target {target}; {n} symbols with that name are declared in the source.")),
|
|
||||||
_ => unreachable!()
|
|
||||||
};
|
|
||||||
|
|
||||||
SymbolTester { parent, semantic, target_symbol_name: target.to_string(), test_result: data }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if the resolved symbol contains all flags in `flags`, using [`SymbolFlags::contains()`]
|
|
||||||
pub fn contains_flags(mut self, flags: SymbolFlags) -> Self {
|
|
||||||
self.test_result = match self.test_result {
|
|
||||||
Ok(symbol_id) => {
|
|
||||||
let found_flags = self.semantic.symbols().get_flag(symbol_id);
|
|
||||||
if found_flags.contains(flags) {
|
|
||||||
Ok(symbol_id)
|
|
||||||
} else {
|
|
||||||
Err(miette!(
|
|
||||||
"Expected {} to contain flags {:?}, but it had {:?}",
|
|
||||||
self.target_symbol_name,
|
|
||||||
flags,
|
|
||||||
found_flags
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err => err,
|
|
||||||
};
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn intersects_flags(mut self, flags: SymbolFlags) -> Self {
|
|
||||||
self.test_result = match self.test_result {
|
|
||||||
Ok(symbol_id) => {
|
|
||||||
let found_flags = self.semantic.symbols().get_flag(symbol_id);
|
|
||||||
if found_flags.intersects(flags) {
|
|
||||||
Ok(symbol_id)
|
|
||||||
} else {
|
|
||||||
Err(miette!(
|
|
||||||
"Expected {} to intersect with flags {:?}, but it had {:?}",
|
|
||||||
self.target_symbol_name,
|
|
||||||
flags,
|
|
||||||
found_flags
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err => err,
|
|
||||||
};
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_number_of_reads(self, ref_count: usize) -> Self {
|
|
||||||
self.has_number_of_references_where(ref_count, Reference::is_read)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn has_number_of_writes(self, ref_count: usize) -> Self {
|
|
||||||
self.has_number_of_references_where(ref_count, Reference::is_write)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_number_of_references(self, ref_count: usize) -> Self {
|
|
||||||
self.has_number_of_references_where(ref_count, |_| true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_number_of_references_where<F>(mut self, ref_count: usize, filter: F) -> Self
|
|
||||||
where
|
|
||||||
F: FnMut(&Reference) -> bool,
|
|
||||||
{
|
|
||||||
self.test_result = match self.test_result {
|
|
||||||
Ok(symbol_id) => {
|
|
||||||
let refs = {
|
|
||||||
self.semantic
|
|
||||||
.symbols()
|
|
||||||
.get_resolved_reference_ids(symbol_id)
|
|
||||||
.iter()
|
|
||||||
.map(|r_id| self.semantic.symbols().get_reference(*r_id).clone())
|
|
||||||
};
|
|
||||||
let num_accepted = refs.filter(filter).count();
|
|
||||||
if num_accepted == ref_count {
|
|
||||||
Ok(symbol_id)
|
|
||||||
} else {
|
|
||||||
Err(miette!("Expected to find {ref_count} acceptable references, but only found {num_accepted}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e => e,
|
|
||||||
};
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::wrong_self_convention)]
|
|
||||||
pub fn is_exported(mut self) -> Self {
|
|
||||||
self.test_result = match self.test_result {
|
|
||||||
Ok(symbol_id) => {
|
|
||||||
let binding = Atom::from(self.target_symbol_name.clone());
|
|
||||||
if self.semantic.module_record().exported_bindings.contains_key(&binding)
|
|
||||||
&& self.semantic.scopes().get_root_binding(&binding) == Some(symbol_id)
|
|
||||||
{
|
|
||||||
Ok(symbol_id)
|
|
||||||
} else {
|
|
||||||
Err(miette!("Expected {binding} to be exported."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e => e,
|
|
||||||
};
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Complete the test case. Will panic if any of the previously applied
|
|
||||||
/// assertions failed.
|
|
||||||
pub fn test(self) {
|
|
||||||
let res: Result<_, _> = self.into();
|
|
||||||
|
|
||||||
res.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<SymbolTester<'a>> for Result<(), Error> {
|
|
||||||
fn from(val: SymbolTester<'a>) -> Self {
|
|
||||||
val.test_result.map(|_| {}).map_err(|e| e.with_source_code(val.parent.source_text))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
251
crates/oxc_semantic/tests/util/symbol_tester.rs
Normal file
251
crates/oxc_semantic/tests/util/symbol_tester.rs
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use oxc_diagnostics::{miette::miette, Error};
|
||||||
|
extern crate miette;
|
||||||
|
use oxc_semantic::{Reference, ScopeFlags, Semantic, SymbolFlags, SymbolId};
|
||||||
|
use oxc_span::Atom;
|
||||||
|
|
||||||
|
use super::{Expect, SemanticTester};
|
||||||
|
|
||||||
|
pub struct SymbolTester<'a> {
|
||||||
|
parent: &'a SemanticTester,
|
||||||
|
/// Reference to semantic analysis results, from [`SemanticTester`]
|
||||||
|
semantic: Rc<Semantic<'a>>,
|
||||||
|
/// Name of the subject symbol
|
||||||
|
target_symbol_name: String,
|
||||||
|
/// Symbol data, or error if not found
|
||||||
|
test_result: Result<SymbolId, oxc_diagnostics::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SymbolTester<'a> {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(super) fn new_at_root(
|
||||||
|
parent: &'a SemanticTester,
|
||||||
|
semantic: Semantic<'a>,
|
||||||
|
target: &str,
|
||||||
|
) -> Self {
|
||||||
|
let decl =
|
||||||
|
semantic.scopes().get_binding(semantic.scopes().root_scope_id(), &Atom::from(target));
|
||||||
|
let data = decl.map_or_else(|| Err(miette!("Could not find declaration for {target}")), Ok);
|
||||||
|
|
||||||
|
SymbolTester {
|
||||||
|
parent,
|
||||||
|
semantic: Rc::new(semantic),
|
||||||
|
target_symbol_name: target.to_string(),
|
||||||
|
test_result: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn new_unique(
|
||||||
|
parent: &'a SemanticTester,
|
||||||
|
semantic: Semantic<'a>,
|
||||||
|
target: &str,
|
||||||
|
) -> Self {
|
||||||
|
let symbols_with_target_name: Vec<_> =
|
||||||
|
semantic.scopes().iter_bindings().filter(|(_, _, name)| name == &target).collect();
|
||||||
|
let data = match symbols_with_target_name.len() {
|
||||||
|
0 => Err(miette!("Could not find declaration for {target}")),
|
||||||
|
1 => Ok(symbols_with_target_name.iter().map(|(_, symbol_id, _)| *symbol_id).next().unwrap()),
|
||||||
|
n if n > 1 => Err(miette!("Couldn't uniquely resolve symbol id for target {target}; {n} symbols with that name are declared in the source.")),
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
SymbolTester {
|
||||||
|
parent,
|
||||||
|
semantic: Rc::new(semantic),
|
||||||
|
target_symbol_name: target.to_string(),
|
||||||
|
test_result: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the resolved symbol contains all flags in `flags`, using [`SymbolFlags::contains()`]
|
||||||
|
pub fn contains_flags(mut self, flags: SymbolFlags) -> Self {
|
||||||
|
self.test_result = match self.test_result {
|
||||||
|
Ok(symbol_id) => {
|
||||||
|
let found_flags = self.semantic.symbols().get_flag(symbol_id);
|
||||||
|
if found_flags.contains(flags) {
|
||||||
|
Ok(symbol_id)
|
||||||
|
} else {
|
||||||
|
Err(miette!(
|
||||||
|
"Expected {} to contain flags {:?}, but it had {:?}",
|
||||||
|
self.target_symbol_name,
|
||||||
|
flags,
|
||||||
|
found_flags
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err => err,
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intersects_flags(mut self, flags: SymbolFlags) -> Self {
|
||||||
|
self.test_result = match self.test_result {
|
||||||
|
Ok(symbol_id) => {
|
||||||
|
let found_flags = self.semantic.symbols().get_flag(symbol_id);
|
||||||
|
if found_flags.intersects(flags) {
|
||||||
|
Ok(symbol_id)
|
||||||
|
} else {
|
||||||
|
Err(miette!(
|
||||||
|
"Expected {} to intersect with flags {:?}, but it had {:?}",
|
||||||
|
self.target_symbol_name,
|
||||||
|
flags,
|
||||||
|
found_flags
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err => err,
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_number_of_reads(self, ref_count: usize) -> Self {
|
||||||
|
self.has_number_of_references_where(ref_count, Reference::is_read)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn has_number_of_writes(self, ref_count: usize) -> Self {
|
||||||
|
self.has_number_of_references_where(ref_count, Reference::is_write)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_number_of_references(self, ref_count: usize) -> Self {
|
||||||
|
self.has_number_of_references_where(ref_count, |_| true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_number_of_references_where<F>(mut self, ref_count: usize, filter: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnMut(&Reference) -> bool,
|
||||||
|
{
|
||||||
|
self.test_result = match self.test_result {
|
||||||
|
Ok(symbol_id) => {
|
||||||
|
let refs = {
|
||||||
|
self.semantic
|
||||||
|
.symbols()
|
||||||
|
.get_resolved_reference_ids(symbol_id)
|
||||||
|
.iter()
|
||||||
|
.map(|r_id| self.semantic.symbols().get_reference(*r_id).clone())
|
||||||
|
};
|
||||||
|
let num_accepted = refs.filter(filter).count();
|
||||||
|
if num_accepted == ref_count {
|
||||||
|
Ok(symbol_id)
|
||||||
|
} else {
|
||||||
|
Err(miette!("Expected to find {ref_count} acceptable references, but only found {num_accepted}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e => e,
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
pub fn is_exported(mut self) -> Self {
|
||||||
|
self.test_result = match self.test_result {
|
||||||
|
Ok(symbol_id) => {
|
||||||
|
let binding = Atom::from(self.target_symbol_name.clone());
|
||||||
|
if self.semantic.module_record().exported_bindings.contains_key(&binding)
|
||||||
|
&& self.semantic.scopes().get_root_binding(&binding) == Some(symbol_id)
|
||||||
|
{
|
||||||
|
Ok(symbol_id)
|
||||||
|
} else {
|
||||||
|
Err(miette!("Expected {binding} to be exported."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e => e,
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
pub fn is_in_scope(mut self, expected_flags: ScopeFlags) -> Self {
|
||||||
|
let target_name: &str = self.target_symbol_name.as_ref();
|
||||||
|
self.test_result = match self.test_result {
|
||||||
|
Ok(symbol_id) => {
|
||||||
|
let scope_id = self.semantic.symbol_scope(symbol_id);
|
||||||
|
let scope_flags = self.semantic.scopes().get_flags(scope_id);
|
||||||
|
if scope_flags.contains(expected_flags) {
|
||||||
|
Ok(symbol_id)
|
||||||
|
} else {
|
||||||
|
Err(miette!("Binding {target_name} is not in a scope with expected flags.\n\tExpected: {expected_flags:?}\n\tActual: {scope_flags:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e => e,
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
pub fn is_not_in_scope(mut self, excluded_flags: ScopeFlags) -> Self {
|
||||||
|
let target_name: &str = self.target_symbol_name.as_ref();
|
||||||
|
self.test_result = match self.test_result {
|
||||||
|
Ok(symbol_id) => {
|
||||||
|
let scope_id = self.semantic.symbol_scope(symbol_id);
|
||||||
|
let scope_flags = self.semantic.scopes().get_flags(scope_id);
|
||||||
|
if scope_flags.contains(excluded_flags) {
|
||||||
|
Err(miette!("Binding {target_name} is in a scope with excluded flags.\n\tExpected: not {excluded_flags:?}\n\tActual: {scope_flags:?}"))
|
||||||
|
} else {
|
||||||
|
Ok(symbol_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e => e,
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Complete the test case. Will panic if any of the previously applied
|
||||||
|
/// assertions failed.
|
||||||
|
pub fn test(self) {
|
||||||
|
let res: Result<_, _> = self.into();
|
||||||
|
|
||||||
|
res.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Expect<(Rc<Semantic<'a>>, SymbolId), bool> for SymbolTester<'a> {
|
||||||
|
fn expect<'e, F>(self, expectation: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnOnce((Rc<Semantic<'a>>, SymbolId)) -> bool,
|
||||||
|
{
|
||||||
|
let Ok(symbol_id) = self.test_result else { return self };
|
||||||
|
let did_pass = expectation((Rc::clone(&self.semantic), symbol_id));
|
||||||
|
if did_pass {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
Self { test_result: Err(miette!("Expectation failed")), ..self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Expect<(Rc<Semantic<'a>>, SymbolId), Result<(), &'static str>> for SymbolTester<'a> {
|
||||||
|
fn expect<'e, F>(self, expectation: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnOnce((Rc<Semantic<'a>>, SymbolId)) -> Result<(), &'static str>,
|
||||||
|
{
|
||||||
|
let Ok(symbol_id) = self.test_result else { return self };
|
||||||
|
let did_pass = expectation((Rc::clone(&self.semantic), symbol_id));
|
||||||
|
if let Err(e) = did_pass {
|
||||||
|
Self { test_result: Err(miette!(e)), ..self }
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Expect<(Rc<Semantic<'a>>, SymbolId), Result<(), Error>> for SymbolTester<'a> {
|
||||||
|
fn expect<'e, F>(self, expectation: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnOnce((Rc<Semantic<'a>>, SymbolId)) -> Result<(), Error>,
|
||||||
|
{
|
||||||
|
let Ok(symbol_id) = self.test_result else { return self };
|
||||||
|
let did_pass = expectation((Rc::clone(&self.semantic), symbol_id));
|
||||||
|
if let Err(e) = did_pass {
|
||||||
|
Self { test_result: Err(e), ..self }
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<SymbolTester<'a>> for Result<(), Error> {
|
||||||
|
fn from(val: SymbolTester<'a>) -> Self {
|
||||||
|
val.test_result.map(|_| {}).map_err(|e| e.with_source_code(val.parent.source_text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -110,6 +110,8 @@ impl SourceType {
|
||||||
pub fn with_module(mut self, yes: bool) -> Self {
|
pub fn with_module(mut self, yes: bool) -> Self {
|
||||||
if yes {
|
if yes {
|
||||||
self.module_kind = ModuleKind::Module;
|
self.module_kind = ModuleKind::Module;
|
||||||
|
} else {
|
||||||
|
self.module_kind = ModuleKind::Script;
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue