mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
307 lines
11 KiB
Rust
307 lines
11 KiB
Rust
use std::rc::Rc;
|
|
|
|
use oxc_diagnostics::{Error, OxcDiagnostic};
|
|
use oxc_semantic::{Reference, ScopeFlags, ScopeId, Semantic, SymbolFlags, SymbolId};
|
|
use oxc_span::CompactStr;
|
|
|
|
use super::{Expect, SemanticTester};
|
|
|
|
#[must_use]
|
|
pub struct SymbolTester<'a> {
|
|
parent: &'a SemanticTester<'a>,
|
|
/// 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, OxcDiagnostic>,
|
|
}
|
|
|
|
impl<'a> SymbolTester<'a> {
|
|
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(), target);
|
|
let data = decl.map_or_else(
|
|
|| Err(OxcDiagnostic::error(format!("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.as_str() == target)
|
|
.collect();
|
|
let data = match symbols_with_target_name.len() {
|
|
0 => Err(OxcDiagnostic::error(format!("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(OxcDiagnostic::error(format!(
|
|
"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,
|
|
}
|
|
}
|
|
|
|
/// Get inner resources without consuming `self`
|
|
pub fn inner(&self) -> (Rc<Semantic<'a>>, SymbolId) {
|
|
(Rc::clone(&self.semantic), *self.test_result.as_ref().unwrap())
|
|
}
|
|
|
|
pub(super) fn new_first_binding(
|
|
parent: &'a SemanticTester,
|
|
semantic: Semantic<'a>,
|
|
target: &str,
|
|
) -> Self {
|
|
let symbols_with_target_name: Option<(ScopeId, SymbolId, &CompactStr)> =
|
|
semantic.scopes().iter_bindings().find(|(_, _, name)| name.as_str() == target);
|
|
|
|
let data = match symbols_with_target_name {
|
|
Some((_, symbol_id, _)) => Ok(symbol_id),
|
|
None => Err(OxcDiagnostic::error(format!("Could not find declaration for {target}"))),
|
|
};
|
|
|
|
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(OxcDiagnostic::error(format!(
|
|
"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(OxcDiagnostic::error(format!(
|
|
"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)
|
|
}
|
|
|
|
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(OxcDiagnostic::error(format!(
|
|
"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 = self.target_symbol_name.clone();
|
|
if self.semantic.symbols().get_flag(symbol_id).is_export() {
|
|
Ok(symbol_id)
|
|
} else {
|
|
Err(OxcDiagnostic::error(format!(
|
|
"Expected {binding} to be exported with SymbolFlags::Export"
|
|
)))
|
|
}
|
|
}
|
|
e => e,
|
|
};
|
|
self
|
|
}
|
|
|
|
#[allow(clippy::wrong_self_convention)]
|
|
pub fn is_not_exported(mut self) -> Self {
|
|
self.test_result = match self.test_result {
|
|
Ok(symbol_id) => {
|
|
let binding = self.target_symbol_name.clone();
|
|
if self.semantic.symbols().get_flag(symbol_id).contains(SymbolFlags::Export) {
|
|
Err(OxcDiagnostic::error(format!(
|
|
"Expected {binding} to not be exported. Symbol has export flag."
|
|
)))
|
|
} else {
|
|
Ok(symbol_id)
|
|
}
|
|
}
|
|
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(OxcDiagnostic::error(format!(
|
|
"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(OxcDiagnostic::error(format!(
|
|
"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(OxcDiagnostic::error("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(OxcDiagnostic::error(e)), ..self }
|
|
} else {
|
|
self
|
|
}
|
|
}
|
|
}
|
|
impl<'a> Expect<(Rc<Semantic<'a>>, SymbolId), Result<(), OxcDiagnostic>> for SymbolTester<'a> {
|
|
fn expect<'e, F>(self, expectation: F) -> Self
|
|
where
|
|
F: FnOnce((Rc<Semantic<'a>>, SymbolId)) -> Result<(), OxcDiagnostic>,
|
|
{
|
|
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 {
|
|
let source_code = val.parent.source_text.to_string();
|
|
val.test_result.map(|_| {}).map_err(|e| e.with_source_code(source_code))
|
|
}
|
|
}
|