mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
f53f0f017e
## Summary - Add `SourceType::script()` constructor for creating script source types - `with_module(bool)` now only sets module mode when `true`, does nothing when `false` - This aligns with the pattern of other `with_*` methods like `with_script()`, `with_jsx()`, etc. ### Migration Before: ```rust // This used to set script mode when is_module is false let source_type = SourceType::default().with_module(is_module); ``` After: ```rust // Start from script and upgrade to module when needed let source_type = SourceType::script().with_module(is_module); ``` 🤖 Generated with [Claude Code](https://claude.ai/code)
269 lines
6.9 KiB
Rust
269 lines
6.9 KiB
Rust
use oxc_ast::AstKind;
|
|
use oxc_semantic::{ScopeFlags, SymbolFlags};
|
|
|
|
use crate::util::{Expect, SemanticTester};
|
|
|
|
#[test]
|
|
fn test_only_program() {
|
|
let tester = SemanticTester::js("let x = 1;");
|
|
tester.has_root_symbol("x").in_scope(ScopeFlags::Top).test();
|
|
|
|
let semantic = tester.build();
|
|
let scopes = semantic.scoping();
|
|
let root = semantic.scoping().root_scope_id();
|
|
|
|
// ScopeTree contains a single root scope
|
|
assert_eq!(scopes.scopes_len(), 1);
|
|
assert!(!scopes.scopes_is_empty());
|
|
|
|
// Root scope is associated with the Program
|
|
let root_node_id = scopes.get_node_id(root);
|
|
let root_node = semantic.nodes().get_node(root_node_id);
|
|
assert!(matches!(root_node.kind(), AstKind::Program(_)));
|
|
|
|
// ancestors
|
|
assert_eq!(scopes.scope_ancestors(root).count(), 1);
|
|
assert!(scopes.scope_parent_id(root).is_none());
|
|
}
|
|
|
|
#[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")
|
|
.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")
|
|
.in_scope(ScopeFlags::Top | ScopeFlags::StrictMode)
|
|
.test();
|
|
|
|
// Script with top-level "use strict"
|
|
SemanticTester::js(
|
|
r#"
|
|
"use strict";
|
|
function foo() {
|
|
return 1
|
|
}
|
|
"#,
|
|
)
|
|
.with_script(true)
|
|
.has_root_symbol("foo")
|
|
.in_scope(ScopeFlags::Top | ScopeFlags::StrictMode)
|
|
.test();
|
|
|
|
// Script without top-level "use strict"
|
|
SemanticTester::js(
|
|
r"
|
|
function foo() {
|
|
return 1
|
|
}
|
|
",
|
|
)
|
|
.with_script(true)
|
|
.has_root_symbol("foo")
|
|
.in_scope(ScopeFlags::Top)
|
|
.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_script(true);
|
|
|
|
tester
|
|
.has_some_symbol("x")
|
|
.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.scoping().scope_parent_id(scope_id) else {
|
|
return Err("Expected x's scope to have a parent");
|
|
};
|
|
let parent_flags = semantic.scoping().scope_flags(parent_scope_id);
|
|
if parent_flags.contains(ScopeFlags::Top) {
|
|
Ok(())
|
|
} else {
|
|
Err("Expected x to be in a top-level function declaration.")
|
|
}
|
|
})
|
|
.test();
|
|
tester.has_some_symbol("foo").not_in_scope(ScopeFlags::StrictMode).test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_switch_case() {
|
|
SemanticTester::js(
|
|
"
|
|
const foo = 1;
|
|
switch (foo) {
|
|
case 1:
|
|
const foo = 2;
|
|
}
|
|
",
|
|
)
|
|
.has_root_symbol("foo")
|
|
.has_number_of_references(1)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_function_parameters() {
|
|
let tester = SemanticTester::js(
|
|
"
|
|
const foo = 2;
|
|
const c = 0;
|
|
function func(a = foo, b = c, c = 0) {
|
|
const foo = 0;
|
|
}
|
|
",
|
|
);
|
|
|
|
tester.has_root_symbol("foo").has_number_of_references(1).test();
|
|
// b = c should reference the third parameter, so root symbol `c`` should have 0 reference
|
|
tester.has_root_symbol("c").has_number_of_references(0).test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_catch_clause_parameters() {
|
|
SemanticTester::js(
|
|
"
|
|
const a = 0;
|
|
try {
|
|
} catch ({ [a]: b }) {
|
|
const a = 1;
|
|
}
|
|
",
|
|
)
|
|
.has_root_symbol("a")
|
|
.has_number_of_references(1)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_enums() {
|
|
let test = SemanticTester::ts(
|
|
"
|
|
enum A {
|
|
X,
|
|
Y,
|
|
Z
|
|
}",
|
|
);
|
|
test.has_root_symbol("A").contains_flags(SymbolFlags::RegularEnum).test();
|
|
test.has_some_symbol("X").contains_flags(SymbolFlags::EnumMember).test();
|
|
|
|
let semantic = test.build();
|
|
let program = semantic
|
|
.nodes()
|
|
.iter()
|
|
.find(|node| matches!(node.kind(), AstKind::Program(_)))
|
|
.expect("No program node found");
|
|
assert_eq!(program.scope_id(), semantic.scoping().root_scope_id());
|
|
|
|
let (enum_node, enum_decl) = semantic
|
|
.nodes()
|
|
.iter()
|
|
.find_map(|node| {
|
|
let e = node.kind().as_ts_enum_declaration()?;
|
|
Some((node, e))
|
|
})
|
|
.expect("Expected TS test case to have an enum declaration for A.");
|
|
|
|
assert_eq!(
|
|
enum_node.scope_id(),
|
|
program.scope_id(),
|
|
"Expected `enum A` to be created in the top-level scope."
|
|
);
|
|
let enum_body_scope_id = enum_decl.body.scope_id.get().expect("Enum body has no scope id");
|
|
assert_ne!(
|
|
enum_node.scope_id(),
|
|
enum_body_scope_id,
|
|
"Enum body nodes should contain the scope ID they create, not the scope ID they're created in."
|
|
);
|
|
assert_eq!(enum_decl.body.members.len(), 3);
|
|
}
|
|
|
|
#[test]
|
|
fn var_hoisting() {
|
|
SemanticTester::js(
|
|
"
|
|
try {} catch (e) {
|
|
var e = 0;
|
|
}
|
|
",
|
|
)
|
|
.has_root_symbol("e")
|
|
// `e` was hoisted to the top scope so the symbol's scope is also the top scope
|
|
.in_scope(ScopeFlags::Top)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_ts_conditional_types() {
|
|
SemanticTester::ts("type A<T> = T extends string ? T : false;")
|
|
.has_some_symbol("T")
|
|
.has_number_of_references(2)
|
|
.test();
|
|
|
|
// Conditional types create a new scope after check_type.
|
|
SemanticTester::ts(
|
|
"type S<A> = A extends (infer B extends number ? string : never) ? B : false;",
|
|
)
|
|
.has_some_symbol("B")
|
|
.has_number_of_references(1)
|
|
.test();
|
|
|
|
// Inferred type parameter is only available within true branch
|
|
SemanticTester::ts("type S<A> = A extends infer R ? never : R")
|
|
.has_some_symbol("R")
|
|
.has_number_of_references(0)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_eval() {
|
|
let direct_evals = ["eval('')", "function foo() { eval('') }"];
|
|
for code in direct_evals {
|
|
let tester = SemanticTester::js(code);
|
|
let semantic = tester.build();
|
|
assert!(semantic.scoping().root_scope_flags().contains_direct_eval());
|
|
}
|
|
|
|
let indirect_evals = [
|
|
"eval?.('')",
|
|
"(0, eval)('')",
|
|
"const myEval = eval; myEval('')",
|
|
"const obj = { eval }; obj.eval('x + y')",
|
|
];
|
|
for code in indirect_evals {
|
|
let tester = SemanticTester::js(code);
|
|
let semantic = tester.build();
|
|
assert!(!semantic.scoping().root_scope_flags().contains_direct_eval());
|
|
}
|
|
}
|