Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ impl<'a> SemanticBuilder<'a> {
self
}

/// Enable or disable building a [`ControlFlowGraph`].
///
/// [`ControlFlowGraph`]: oxc_cfg::ControlFlowGraph
#[must_use]
pub fn with_cfg(mut self, cfg: bool) -> Self {
self.cfg = if cfg { Some(ControlFlowGraphBuilder::default()) } else { None };
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_semantic/tests/integration/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ fn test_types_simple() {
.has_some_symbol("T")
.contains_flags(SymbolFlags::TypeParameter)
.has_number_of_references(1)
.has_number_of_references_where(1, oxc_semantic::Reference::is_type)
.has_number_of_references_where(1, Reference::is_type)
.test();

SemanticTester::ts("function foo<T>(a: T): void {}")
Expand Down
27 changes: 27 additions & 0 deletions crates/oxc_semantic/tests/integration/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ pub use symbol_tester::SymbolTester;

#[must_use]
pub struct SemanticTester<'a> {
/// Memory arena that AST and [`Semantic`] will store data in.
allocator: Allocator,
/// Source type of the test case.
source_type: SourceType,
/// The source text of the test case.
source_text: &'a str,
/// Build a [`ControlFlowGraph`]?
///
/// [`ControlFlowGraph`]: oxc_cfg::ControlFlowGraph
cfg: bool,
/// Expect semantic analysis to produce errors.
///
Expand Down Expand Up @@ -47,6 +53,10 @@ impl<'a> SemanticTester<'a> {
Self::new(source_text, SourceType::default().with_module(true))
}

/// Create a new tester for some source text.
///
/// You may find one of the other factory methods, like
/// [`SemanticTester::ts`] or [`SemanticTester::js`], more convenient.
pub fn new(source_text: &'a str, source_type: SourceType) -> Self {
Self {
allocator: Allocator::default(),
Expand All @@ -64,20 +74,27 @@ impl<'a> SemanticTester<'a> {
}

/// Mark the [`SourceType`] as JSX
///
/// If the source is currently TypeScript, it will become TSX.
pub fn with_jsx(mut self, yes: bool) -> Self {
self.source_type = self.source_type.with_jsx(yes);
self
}

/// Mark the [`SourceType`] as an ESM module.
pub fn with_module(mut self, yes: bool) -> Self {
self.source_type = self.source_type.with_module(yes);
self
}

/// Enable or disable building a [`ControlFlowGraph`].
///
/// [`ControlFlowGraph`]: oxc_cfg::ControlFlowGraph
pub fn with_cfg(mut self, yes: bool) -> Self {
self.cfg = yes;
self
}

/// The program being tested is expected to produce errors during semantic analysis.
///
/// By default, programs are expected to be error-free.
Expand Down Expand Up @@ -201,6 +218,16 @@ impl<'a> SemanticTester<'a> {
ClassTester::has_class(self.build(), name)
}

/// Check if semantic analysis produces an error matching `message`.
///
/// Matching is done using `.contains()`.
///
/// ## Fails
/// - If [`parsing`] produces errors
/// - If [`SemanticBuilder`] finishes without producing any errors.
/// - If [`SemanticBuilder`] finishes with errors, but none of them match `message`
///
/// [`parsing`]: oxc_parser::Parser::parse
pub fn has_error(&self, message: &str) {
let SemanticBuilderReturn { errors, .. } = self.build_with_errors();
assert!(
Expand Down
45 changes: 45 additions & 0 deletions crates/oxc_semantic/tests/integration/util/symbol_tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,30 @@ use oxc_span::CompactStr;

use super::{Expect, SemanticTester};

/// Test a symbol in the [`Semantic`] analysis results.
///
/// To use this, chain together assertions about the symbol, such as
/// [`SymbolTester::contains_flags`], [`SymbolTester::has_number_of_reads`],
/// etc., then finish the chain off with a call to [`SymbolTester::test`].
///
/// You will never create this struct manually. Instead, use one of
/// [`SemanticTester`]'s factories, such as [`SemanticTester::has_root_symbol`].
///
/// # Example
/// ```
/// use oxc_semantic::{SymbolFlags, Semantic};
/// use super::SemanticTester;
///
/// #[test]
/// fn my_test() {
/// SemanticTester::js("let x = 0; let foo = (0, x++)")
/// .has_some_symbol("x") // find a symbol named "x" at any scope
/// .contains_flags(SymbolFlags::Variable) // check that it's a variable
/// .has_number_of_reads(1) // check read references
/// .has_number_of_writes(1) // check write references
/// .test(); // finish the test. Will panic if any assertions failed.
/// }
///```
#[must_use]
pub struct SymbolTester<'a> {
parent: &'a SemanticTester<'a>,
Expand Down Expand Up @@ -131,18 +155,27 @@ impl<'a> SymbolTester<'a> {
self
}

/// Check that this symbol has a certain number of read [`Reference`]s
///
/// References that are both read and write are counted.
pub fn has_number_of_reads(self, ref_count: usize) -> Self {
self.has_number_of_references_where(ref_count, Reference::is_read)
}

/// Check that this symbol has a certain number of write [`Reference`]s.
///
/// References that are both read and write are counted.
pub fn has_number_of_writes(self, ref_count: usize) -> Self {
self.has_number_of_references_where(ref_count, Reference::is_write)
}

/// Check that this symbol has a certain number of [`Reference`]s of any kind.
pub fn has_number_of_references(self, ref_count: usize) -> Self {
self.has_number_of_references_where(ref_count, |_| true)
}

/// Check that this symbol has a certain number of [`Reference`]s that meet
/// some criteria established by a predicate.
pub fn has_number_of_references_where<F>(mut self, ref_count: usize, filter: F) -> Self
where
F: FnMut(&Reference) -> bool,
Expand Down Expand Up @@ -170,6 +203,12 @@ impl<'a> SymbolTester<'a> {
self
}

/// Check that this symbol is exported.
///
/// Export status is checked using the symbol's [`SymbolFlags`], not by
/// checking the [`oxc_semantic::ModuleRecord`].
///
/// For the inverse of this assertion, use [`SymbolTester::is_not_exported`].
#[allow(clippy::wrong_self_convention)]
pub fn is_exported(mut self) -> Self {
self.test_result = match self.test_result {
Expand All @@ -188,6 +227,12 @@ impl<'a> SymbolTester<'a> {
self
}

/// Check that this symbol is not exported.
///
/// Export status is checked using the symbol's [`SymbolFlags`], not by
/// checking the [`oxc_semantic::ModuleRecord`].
///
/// For the inverse of this assertion, use [`SymbolTester::is_exported`].
#[allow(clippy::wrong_self_convention)]
pub fn is_not_exported(mut self) -> Self {
self.test_result = match self.test_result {
Expand Down