Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(semantic): add scope tests for IIFEs #4451

Closed
Closed
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
57 changes: 56 additions & 1 deletion crates/oxc_semantic/tests/integration/scopes.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use oxc_semantic::ScopeFlags;
use oxc_ast::AstKind;
use oxc_semantic::{ScopeFlags, SymbolFlags};

use crate::util::{Expect, SemanticTester};

Expand Down Expand Up @@ -106,6 +107,60 @@ fn test_switch_case() {
.test();
}

#[allow(clippy::disallowed_names)]
#[test]
fn test_function_scopes() {
let test = SemanticTester::ts("function foo() { return foo() }");
let foo_id = test
.has_root_symbol("foo")
.contains_flags(SymbolFlags::Function)
.contains_flags(SymbolFlags::BlockScopedVariable)
.has_number_of_reads(1)
.test();

let semantic = test.build();
let root_id = semantic.scopes().root_scope_id();
let foo_scope_id = semantic.symbols().get_scope_id(foo_id);
assert_eq!(foo_scope_id, root_id, "Expected fn foo to be in the root scope.");

let foo_node = semantic.nodes().get_node(semantic.symbols().get_declaration(foo_id));
let AstKind::Function(foo) = foo_node.kind() else {
panic!("Expected foo's declaration node to be a FunctionDeclaration");
};
assert!(foo.is_declaration(), "Expected foo's declaration node to be a FunctionDeclaration");
assert_eq!(foo_node.scope_id(), root_id, "Expected fn foo to be in the root scope.");
assert_ne!(
foo.scope_id.get().unwrap(),
root_id,
"function bodies should not be the root scope"
);

let binding_id = semantic
.scopes()
.get_binding(root_id, "foo")
.expect("Expected to find a binding for fn foo");
assert_eq!(binding_id, foo_id);

// =========================================================================

let test = SemanticTester::ts("(function foo() { return foo() })()");
let foo_id = test
.has_some_symbol("foo")
.contains_flags(SymbolFlags::Function)
.does_not_contain_flags(SymbolFlags::BlockScopedVariable)
.has_number_of_reads(1)
.test();

let semantic = test.build();
let root_id = semantic.scopes().root_scope_id();

let foo_node = semantic.nodes().get_node(semantic.symbols().get_declaration(foo_id));
let foo_scope_id = semantic.symbols().get_scope_id(foo_id);
assert_eq!(foo_node.scope_id(), root_id);
// FIXME: These should be equal
assert_ne!(foo_node.scope_id(), foo_scope_id);
}

#[test]
fn test_function_parameters() {
let tester = SemanticTester::js(
Expand Down
9 changes: 9 additions & 0 deletions crates/oxc_semantic/tests/integration/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ fn test_function_expressions() {
.test();
}

#[test]
fn test_function_iifes() {
SemanticTester::ts("(function foo() {})(); foo()")
.has_some_symbol("foo")
.contains_flags(SymbolFlags::Function)
.has_number_of_references(0)
.test();
}

#[test]
fn test_var_simple() {
SemanticTester::js("let x; { let y; }")
Expand Down
35 changes: 29 additions & 6 deletions crates/oxc_semantic/tests/integration/util/symbol_tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,24 @@ impl<'a> SymbolTester<'a> {
self
}

pub fn does_not_contain_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) {
Err(OxcDiagnostic::error(format!(
"Expected {} to not contain flags {:?}, but it had {:?}",
self.target_symbol_name, flags, found_flags
)))
} else {
Ok(symbol_id)
}
}
err => err,
};
self
}

/// Check that this symbol has a certain number of read [`Reference`]s
///
/// References that are both read and write are counted.
Expand Down Expand Up @@ -292,11 +310,16 @@ impl<'a> SymbolTester<'a> {
}

/// Complete the test case. Will panic if any of the previously applied
/// assertions failed.
pub fn test(self) {
let res: Result<_, _> = self.into();
/// assertions failed. If all assertions pass, this function will return
/// the [`SymbolId`] of the target symbol.
///
/// # Panics
/// - If any of the previously applied assertions failed.
/// - If the symbol could not be found in the semantic analysis results.
pub fn test(self) -> SymbolId {
let res: Result<SymbolId, _> = self.into();

res.unwrap();
res.unwrap()
}
}

Expand Down Expand Up @@ -344,9 +367,9 @@ impl<'a> Expect<(Rc<Semantic<'a>>, SymbolId), Result<(), OxcDiagnostic>> for Sym
}
}

impl<'a> From<SymbolTester<'a>> for Result<(), Error> {
impl<'a> From<SymbolTester<'a>> for Result<SymbolId, 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))
val.test_result.map_err(|e| e.with_source_code(source_code))
}
}