mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
Use reference write flags to reject strict-mode assignments to `eval` and `arguments`, including for-in/of, while accepting reads in destructuring keys and defaults. Preserve those flags across TypeScript assertion annotations. Validated scoped Rust checks, semantic conformance, and reference/diagnostic regressions. AI-assisted (Codex).
757 lines
23 KiB
Rust
757 lines
23 KiB
Rust
use oxc_semantic::{Reference, ReferenceFlags, SymbolFlags};
|
|
use oxc_span::SourceType;
|
|
|
|
use crate::util::SemanticTester;
|
|
|
|
fn assert_root_and_single_local_reference_counts(
|
|
tester: &SemanticTester<'_>,
|
|
expected: &[(&str, usize, usize)],
|
|
) {
|
|
let semantic = tester.build();
|
|
let scoping = semantic.scoping();
|
|
let root_scope_id = scoping.root_scope_id();
|
|
|
|
for &(name, root_count, local_count) in expected {
|
|
let root_symbol = scoping.get_binding(root_scope_id, name.into()).unwrap();
|
|
let local_symbol = scoping
|
|
.symbol_ids()
|
|
.find(|&symbol_id| {
|
|
scoping.symbol_name(symbol_id) == name
|
|
&& scoping.symbol_scope_id(symbol_id) != root_scope_id
|
|
})
|
|
.unwrap();
|
|
|
|
assert_eq!(scoping.get_resolved_references(root_symbol).count(), root_count, "{name}");
|
|
assert_eq!(scoping.get_resolved_references(local_symbol).count(), local_count, "{name}");
|
|
}
|
|
}
|
|
|
|
/// `delete a?.b` and `delete (a.b)` are property writes, same as `delete a.b`.
|
|
/// https://github.com/oxc-project/oxc/issues/27419
|
|
#[test]
|
|
fn test_delete_member_write_target() {
|
|
for code in [
|
|
"delete a.b",
|
|
"delete a.b.c",
|
|
"delete a?.b",
|
|
"delete a?.b.c",
|
|
"delete a.b?.c",
|
|
"delete (a.b)",
|
|
] {
|
|
let source = format!("const a = {{}}; {code};");
|
|
let source: &'static str = Box::leak(source.into_boxed_str());
|
|
let tester = SemanticTester::js(source);
|
|
let semantic = tester.build();
|
|
let scoping = semantic.scoping();
|
|
let symbol = scoping.get_root_binding("a".into()).unwrap();
|
|
let flags: Vec<_> =
|
|
scoping.get_resolved_references(symbol).map(oxc_semantic::Reference::flags).collect();
|
|
assert_eq!(flags, vec![ReferenceFlags::Read | ReferenceFlags::MemberWriteTarget], "{code}");
|
|
}
|
|
|
|
// Bare `delete` is not a property modification.
|
|
SemanticTester::js("let x; delete x;")
|
|
.with_script(true)
|
|
.has_some_symbol("x")
|
|
.has_number_of_references_where(0, |reference| reference.flags().is_member_write_target())
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_type_assertion_reference_flags() {
|
|
for (annotation, type_references) in [
|
|
("any", &[][..]),
|
|
("T", &["T"][..]),
|
|
("T[]", &["T"][..]),
|
|
("Box<T>", &["Box", "T"][..]),
|
|
("typeof typeValue", &["typeValue"][..]),
|
|
] {
|
|
for (expression, expected_flags) in [
|
|
(format!("[<{annotation}>value] = []"), ReferenceFlags::Write),
|
|
(format!("({{ key: <{annotation}>value }} = {{}})"), ReferenceFlags::Write),
|
|
(format!("(<{annotation}>value) += 1"), ReferenceFlags::read_write()),
|
|
(format!("(<{annotation}>value)++"), ReferenceFlags::read_write()),
|
|
(format!("for (<{annotation}>value of []) {{}}"), ReferenceFlags::Write),
|
|
(
|
|
format!("(<{annotation}>value).key = 1"),
|
|
ReferenceFlags::Read | ReferenceFlags::MemberWriteTarget,
|
|
),
|
|
(format!("<{annotation}>value"), ReferenceFlags::Read),
|
|
(format!("[other = <{annotation}>value] = []"), ReferenceFlags::Read),
|
|
(format!("({{ [<{annotation}>value]: other }} = {{}})"), ReferenceFlags::Read),
|
|
] {
|
|
let source = format!(
|
|
"type T = any; type Box<V> = V; let typeValue, value, other; {expression};"
|
|
);
|
|
let tester = SemanticTester::new(&source, SourceType::ts());
|
|
let semantic = tester.build();
|
|
let scoping = semantic.scoping();
|
|
for (name, expected) in std::iter::once(("value", expected_flags))
|
|
.chain(type_references.iter().map(|&name| (name, ReferenceFlags::Type)))
|
|
{
|
|
let symbol = scoping.get_root_binding(name.into()).unwrap();
|
|
let flags: Vec<_> =
|
|
scoping.get_resolved_references(symbol).map(Reference::flags).collect();
|
|
assert_eq!(flags, vec![expected], "{source}: {name}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_strict_type_assertion_assignments() {
|
|
for name in ["eval", "arguments"] {
|
|
for annotation in ["any", "T", "T[]", "typeof eval"] {
|
|
for (expression, is_write) in [
|
|
(format!("[<{annotation}>{name}] = []"), true),
|
|
(format!("({{ value: <{annotation}>{name} }} = {{}})"), true),
|
|
(format!("[...<{annotation}>{name}] = []"), true),
|
|
(format!("[<{annotation}>{name} = value] = []"), true),
|
|
(format!("[<{annotation}><{annotation}>{name}] = []"), true),
|
|
(format!("for (<{annotation}>{name} of []) {{}}"), true),
|
|
(format!("(<{annotation}>{name})++"), true),
|
|
(format!("<{annotation}>{name}"), false),
|
|
(format!("[value = <{annotation}>{name}] = []"), false),
|
|
(format!("({{ [<{annotation}>{name}]: value }} = {{}})"), false),
|
|
(format!("(<{annotation}>{name}).prop = 1"), false),
|
|
(format!("[<{annotation}>{name}.prop] = []"), false),
|
|
] {
|
|
let source = format!("\"use strict\"; type T = any; let value; {expression};");
|
|
let tester = SemanticTester::new(&source, SourceType::ts()).with_script(true);
|
|
let result = tester.build_with_errors();
|
|
let messages: Vec<_> = result.diagnostics.iter().map(ToString::to_string).collect();
|
|
let expected = if is_write {
|
|
vec![format!("Cannot assign to '{name}' in strict mode")]
|
|
} else {
|
|
vec![]
|
|
};
|
|
assert_eq!(messages, expected, "{source}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_class_simple() {
|
|
SemanticTester::js("export class Foo {};")
|
|
.has_root_symbol("Foo")
|
|
.contains_flags(SymbolFlags::Class)
|
|
.has_number_of_references(0)
|
|
.test();
|
|
|
|
SemanticTester::js("class Foo {}; let f = new Foo()")
|
|
.has_root_symbol("Foo")
|
|
.has_number_of_reads(1)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_function_simple() {
|
|
SemanticTester::js("function foo() { return }")
|
|
.has_root_symbol("foo")
|
|
.contains_flags(SymbolFlags::Function)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_function_expressions() {
|
|
SemanticTester::js("const x = function y() {}")
|
|
.has_some_symbol("y")
|
|
.contains_flags(SymbolFlags::Function)
|
|
.test();
|
|
SemanticTester::js("const x = () => {}")
|
|
.has_some_symbol("x")
|
|
.contains_flags(SymbolFlags::BlockScopedVariable | SymbolFlags::ConstVariable)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_var_simple() {
|
|
SemanticTester::js("let x; { let y; }")
|
|
.has_some_symbol("x")
|
|
.intersects_flags(SymbolFlags::Variable)
|
|
.contains_flags(SymbolFlags::BlockScopedVariable)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_hoisted_var_does_not_reuse_block_type_binding() {
|
|
for source in [
|
|
"function f() { { type X = string; var X = 1; type Y = X; X; } X; }",
|
|
"function f() { { interface X {} var X = 1; type Y = X; X; } X; }",
|
|
"function f() { { type X = string; { var X = 1; } type Y = X; X; } X; }",
|
|
] {
|
|
let tester = SemanticTester::ts(source);
|
|
let semantic = tester.build();
|
|
let scoping = semantic.scoping();
|
|
let symbols = scoping
|
|
.symbol_ids()
|
|
.filter(|&symbol| scoping.symbol_name(symbol) == "X")
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(symbols.len(), 2, "{source}");
|
|
let value_symbol = symbols
|
|
.iter()
|
|
.copied()
|
|
.find(|&symbol| scoping.symbol_flags(symbol).is_function_scoped_declaration())
|
|
.unwrap();
|
|
let type_symbol = symbols.into_iter().find(|&symbol| symbol != value_symbol).unwrap();
|
|
assert_ne!(scoping.symbol_scope_id(value_symbol), scoping.symbol_scope_id(type_symbol));
|
|
assert_eq!(scoping.get_resolved_references(value_symbol).count(), 2);
|
|
assert_eq!(scoping.get_resolved_references(type_symbol).count(), 1);
|
|
assert!(scoping.get_resolved_references(value_symbol).all(Reference::is_read));
|
|
assert!(scoping.get_resolved_references(type_symbol).all(Reference::is_type));
|
|
assert!(!scoping.root_unresolved_references().contains_key("X"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_var_read_write() {
|
|
SemanticTester::js("let x; x += 1")
|
|
.has_root_symbol("x")
|
|
.has_number_of_references(1)
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(1)
|
|
.test();
|
|
|
|
SemanticTester::js("let x = 0; let foo = (0, x++)")
|
|
.has_some_symbol("x")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(1)
|
|
.test();
|
|
|
|
SemanticTester::js("let x = 0; x++")
|
|
.has_some_symbol("x")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(1)
|
|
.test();
|
|
|
|
SemanticTester::js("let a; let b = 1 + (0, ((a)));")
|
|
.has_some_symbol("a")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(0)
|
|
.test();
|
|
|
|
SemanticTester::js(
|
|
"
|
|
let x;
|
|
function foo(a) {
|
|
console.log(x(a))
|
|
}",
|
|
)
|
|
.has_some_symbol("x")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(0)
|
|
.test();
|
|
}
|
|
|
|
// https://github.com/rolldown/rolldown/issues/10722
|
|
#[test]
|
|
fn test_forward_outer_binding_in_nested_default_parameter() {
|
|
let tester = SemanticTester::js(
|
|
"let var_n = 0, let_n = 0, fn_n = 0;
|
|
function outer() {
|
|
const read_var = function(a = var_n) { return a };
|
|
const read_let = function(a = let_n) { return a };
|
|
const read_fn = function(a = fn_n) { return a };
|
|
var var_n = 1;
|
|
let let_n = 1;
|
|
function fn_n() {}
|
|
return [read_var, read_let, read_fn];
|
|
}
|
|
use(var_n, let_n, fn_n, outer);",
|
|
);
|
|
assert_root_and_single_local_reference_counts(
|
|
&tester,
|
|
&[("var_n", 1, 1), ("let_n", 1, 1), ("fn_n", 1, 1)],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_nested_default_parameter_does_not_see_own_function_body() {
|
|
let tester = SemanticTester::js(
|
|
"let n = 0;
|
|
function outer() {
|
|
const read = function(a = n) {
|
|
var n = 1;
|
|
return a;
|
|
};
|
|
return read;
|
|
}
|
|
use(n, outer);",
|
|
);
|
|
assert_root_and_single_local_reference_counts(&tester, &[("n", 2, 0)]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_nested_default_parameter_in_outer_parameter_skips_outer_function_body() {
|
|
let tester = SemanticTester::js(
|
|
"let n = 0;
|
|
function outer(read = function(a = n) { return a }) {
|
|
var n = 1;
|
|
return read;
|
|
}
|
|
use(n, outer);",
|
|
);
|
|
assert_root_and_single_local_reference_counts(&tester, &[("n", 2, 0)]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_nested_default_parameter_sees_later_outer_parameter() {
|
|
SemanticTester::js(
|
|
"function outer(read = function(a = n) { return a }, n) {
|
|
return read;
|
|
}
|
|
use(outer);",
|
|
)
|
|
.has_some_symbol("n")
|
|
.has_number_of_references(1)
|
|
.test();
|
|
}
|
|
|
|
// https://github.com/oxc-project/oxc/issues/20427
|
|
// https://github.com/oxc-project/oxc/issues/22783
|
|
#[test]
|
|
fn test_function_signature_does_not_see_function_body_bindings() {
|
|
let tester = SemanticTester::ts(
|
|
"type T = number;
|
|
let value = 0;
|
|
function f(a: T): typeof value {
|
|
type T = string;
|
|
var value = 1;
|
|
return value;
|
|
}
|
|
use(value, f);",
|
|
);
|
|
assert_root_and_single_local_reference_counts(&tester, &[("T", 1, 0), ("value", 2, 1)]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_arrow_parameter_does_not_see_arrow_body_binding() {
|
|
let tester = SemanticTester::js(
|
|
"let n = 0;
|
|
const read = (a = n) => {
|
|
let n = 1;
|
|
return a;
|
|
};
|
|
use(n, read);",
|
|
);
|
|
assert_root_and_single_local_reference_counts(&tester, &[("n", 2, 0)]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_types_simple() {
|
|
let test = SemanticTester::ts(
|
|
"
|
|
interface A {
|
|
x: number;
|
|
y: string;
|
|
}
|
|
type T = { x: number; y: string; }
|
|
|
|
const t: T = { x: 1, y: 'foo' };
|
|
",
|
|
);
|
|
test.has_root_symbol("A")
|
|
.contains_flags(SymbolFlags::Interface)
|
|
.has_number_of_references(0)
|
|
.test();
|
|
|
|
test.has_root_symbol("T")
|
|
.contains_flags(SymbolFlags::TypeAlias)
|
|
.has_number_of_references(1)
|
|
.test();
|
|
|
|
SemanticTester::ts("function foo<T>(): T { }")
|
|
.has_some_symbol("T")
|
|
.contains_flags(SymbolFlags::TypeParameter)
|
|
.has_number_of_references(1)
|
|
.has_number_of_references_where(1, Reference::is_type)
|
|
.test();
|
|
|
|
SemanticTester::ts("function foo<T>(a: T): void {}")
|
|
.has_some_symbol("T")
|
|
.contains_flags(SymbolFlags::TypeParameter)
|
|
.has_number_of_references(1)
|
|
.has_number_of_references_where(1, Reference::is_type)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_ts_type_alias_declaration() {
|
|
let tester = SemanticTester::ts(
|
|
"
|
|
type A<AB> = AB
|
|
type B<AB> = AB
|
|
",
|
|
);
|
|
|
|
tester
|
|
.has_symbol("AB")
|
|
.contains_flags(SymbolFlags::TypeParameter)
|
|
.has_number_of_references(1)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_function_with_type_parameter() {
|
|
let tester = SemanticTester::ts(
|
|
"
|
|
function mdl<M>() {
|
|
function mdl2() {
|
|
function mdl3() {
|
|
return '' as M;
|
|
}
|
|
}
|
|
}
|
|
function kfc<K>() {
|
|
function kfc2<K>() {
|
|
function kfc3<K>() {
|
|
return '' as K;
|
|
}
|
|
}
|
|
}
|
|
",
|
|
);
|
|
|
|
tester.has_symbol("M").has_number_of_references(1).test();
|
|
tester.has_symbol("K").has_number_of_references(0).test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_class_with_type_parameter() {
|
|
let tester = SemanticTester::ts(
|
|
"
|
|
class Cls<T> {
|
|
a: T;
|
|
method<K>(b: T | K) {}
|
|
func() {
|
|
type D = T;
|
|
return null as D
|
|
}
|
|
}
|
|
type B = any;
|
|
class ClassB<B> {
|
|
b: B;
|
|
}
|
|
",
|
|
);
|
|
|
|
tester.has_symbol("T").has_number_of_references(3).test();
|
|
tester.has_symbol("K").has_number_of_references(1).test();
|
|
tester.has_symbol("D").has_number_of_references(1).test();
|
|
|
|
// type B is not referenced
|
|
tester.has_symbol("B").has_number_of_references(0).test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_class_with_accessor() {
|
|
SemanticTester::ts(
|
|
"
|
|
type T = 1;
|
|
|
|
abstract class Foo {
|
|
accessor prop: T;
|
|
}
|
|
",
|
|
)
|
|
.has_some_symbol("T")
|
|
.has_number_of_references(1)
|
|
.has_number_of_references_where(1, Reference::is_type)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_ts_mapped_type() {
|
|
let tester = SemanticTester::ts(
|
|
"
|
|
type M<T> = { [K in keyof T]: T[K] };
|
|
type Y = any;
|
|
type X<T> = { [Y in keyof T]: T[Y] };
|
|
",
|
|
);
|
|
|
|
tester.has_symbol("T").has_number_of_references(2).test();
|
|
tester.has_symbol("K").has_number_of_references(1).test();
|
|
|
|
// type Y is not referenced
|
|
tester.has_symbol("Y").has_number_of_references(0).test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_ts_interface_declaration_with_type_parameter() {
|
|
let tester = SemanticTester::ts(
|
|
"
|
|
type A = any;
|
|
interface ITA<A> {
|
|
a: A;
|
|
}
|
|
interface ITB<B> {
|
|
b: B;
|
|
}
|
|
",
|
|
);
|
|
|
|
tester.has_symbol("B").has_number_of_references(1).test();
|
|
|
|
// type A is not referenced
|
|
tester.has_symbol("A").has_number_of_references(0).test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_ts_infer_type() {
|
|
let tester = SemanticTester::ts(
|
|
"
|
|
type T = T extends infer U ? U : never;
|
|
|
|
type C = any;
|
|
type K = K extends infer C ? K : never;
|
|
",
|
|
);
|
|
|
|
tester.has_symbol("T").has_number_of_references(1).test();
|
|
tester.has_symbol("U").has_number_of_references(1).test();
|
|
|
|
// type C is not referenced
|
|
tester.has_symbol("C").has_number_of_references(0).test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_value_used_as_type() {
|
|
// Type annotations (or any type reference) do not resolve to value symbols
|
|
SemanticTester::ts(
|
|
"
|
|
const x = 1;
|
|
function foo(a: x) { }
|
|
",
|
|
)
|
|
.has_root_symbol("x")
|
|
.intersects_flags(SymbolFlags::Value)
|
|
.has_number_of_references(0)
|
|
.test();
|
|
|
|
// T is a value that gets shadowed by a type. When `T` is referenced within
|
|
// a value context, the root `const T` should be the symbol recorded in the
|
|
// reference.
|
|
let tester = SemanticTester::ts(
|
|
"
|
|
const T = 1;
|
|
function foo<T extends number>(a: T) {
|
|
return a + T;
|
|
}
|
|
",
|
|
);
|
|
|
|
tester.has_root_symbol("T").has_number_of_reads(1).test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_type_used_as_value() {
|
|
SemanticTester::ts(
|
|
"
|
|
type T = number;
|
|
let x = T;
|
|
",
|
|
)
|
|
.has_some_symbol("T")
|
|
.has_number_of_reads(0)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_type_query() {
|
|
SemanticTester::ts(
|
|
"
|
|
type T = number;
|
|
let x: typeof T;
|
|
",
|
|
)
|
|
.has_some_symbol("T")
|
|
.has_number_of_reads(0)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_arrow_implicit_return() {
|
|
SemanticTester::js("let i = 0; const x = () => i")
|
|
.has_root_symbol("i")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(0)
|
|
.test();
|
|
|
|
SemanticTester::js("let i = 0; const x = () => ++i")
|
|
.has_root_symbol("i")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(1)
|
|
.test();
|
|
|
|
SemanticTester::js("let i = 0; const x = () => { ++i }")
|
|
.has_root_symbol("i")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(1)
|
|
.test();
|
|
|
|
SemanticTester::js("let i = 0; const x = () => (0, ++i)")
|
|
.has_root_symbol("i")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(1)
|
|
.test();
|
|
|
|
SemanticTester::js("let i = 0; const x = () => (++i, 0)")
|
|
.has_root_symbol("i")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(1)
|
|
.test();
|
|
|
|
SemanticTester::js("let i = 1; const foo = () => () => { i++ }")
|
|
.has_root_symbol("i")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(1)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_arrow_explicit_return() {
|
|
SemanticTester::js("let i = 0; const x = () => { return i }")
|
|
.has_root_symbol("i")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(0)
|
|
.test();
|
|
|
|
SemanticTester::js("let i = 0; const x = () => { return ++i }")
|
|
.has_root_symbol("i")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(1)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_tagged_templates() {
|
|
// https://github.com/oxc-project/oxc/issues/5391
|
|
SemanticTester::tsx(
|
|
"
|
|
import styled from 'styled-components';
|
|
|
|
import { Prose, ProseProps } from './prose';
|
|
|
|
interface Props extends ProseProps {
|
|
density?: number;
|
|
}
|
|
export const HandMarkedPaperBallotProse = styled(Prose)<Props>`
|
|
line-height: ${({ density }) => (density !== 0 ? '1.1' : '1.3')};
|
|
`;
|
|
",
|
|
)
|
|
.has_some_symbol("density")
|
|
.has_number_of_reads(1)
|
|
.has_number_of_writes(0)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_module_like_declarations() {
|
|
SemanticTester::ts("namespace A { export const x = 1; }")
|
|
.has_root_symbol("A")
|
|
.contains_flags(SymbolFlags::ValueModule)
|
|
.test();
|
|
|
|
SemanticTester::ts("module A { export const x = 1; }")
|
|
.has_root_symbol("A")
|
|
.contains_flags(SymbolFlags::ValueModule)
|
|
.test();
|
|
|
|
SemanticTester::ts("module A { export type x = 1; }")
|
|
.has_root_symbol("A")
|
|
.contains_flags(SymbolFlags::NamespaceModule)
|
|
.test();
|
|
|
|
let test = SemanticTester::ts("declare global { interface Window { x: number; } }");
|
|
let semantic = test.build();
|
|
let global = semantic.scoping().symbol_names().find(|name| *name == "global");
|
|
assert!(
|
|
global.is_none(),
|
|
"A symbol should not be created for global augmentation declarations."
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_class_merging() {
|
|
// classes can be merged with interfaces, resulting in a single symbol
|
|
SemanticTester::ts(
|
|
"
|
|
class Foo {}
|
|
interface Foo {}
|
|
",
|
|
)
|
|
.has_root_symbol("Foo")
|
|
.contains_flags(SymbolFlags::Class)
|
|
.contains_flags(SymbolFlags::Interface)
|
|
.test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_redeclaration() {
|
|
SemanticTester::js("(function n(n) { console.log (n) }) ()")
|
|
.has_symbol("n") // the first `n` is the function expression
|
|
.equal_flags(SymbolFlags::Function | SymbolFlags::FunctionExpression) // param `n` is a SymbolFlags::FunctionScopedVariable
|
|
.has_number_of_references(0) // no references to the function
|
|
.test();
|
|
}
|
|
|
|
// https://github.com/oxc-project/oxc/issues/18719
|
|
#[test]
|
|
fn test_function_var_redeclaration_in_module() {
|
|
// Both orderings should produce a redeclaration error in module mode
|
|
SemanticTester::js("function a() {} var a = ''").has_error("already been declared");
|
|
SemanticTester::js("var a = ''; function a() {}").has_error("already been declared");
|
|
}
|
|
|
|
#[test]
|
|
fn test_enum_member_redeclaration_with_lone_surrogate() {
|
|
// Enum member symbols store UTF-8 names, so the checker compares these names itself.
|
|
SemanticTester::ts(r#"enum A { "\uD800" = 1, "\uD800" = 2 }"#)
|
|
.has_error("Identifier `\\ud800` has already been declared");
|
|
SemanticTester::ts(r#"enum B { ["\uDC00"] = 1, [`\uDC00`] = 2 }"#)
|
|
.has_error("Identifier `\\udc00` has already been declared");
|
|
SemanticTester::ts(r#"enum C { "\uD800" = 1, "\uDC00" = 2, "\uFFFD" = 3 }"#).build();
|
|
}
|
|
|
|
// https://github.com/oxc-project/oxc/issues/15961
|
|
#[test]
|
|
fn test_import_value_redeclaration_in_module() {
|
|
// Import + var
|
|
SemanticTester::js("import { Foo } from './foo.js'; var Foo;")
|
|
.has_error("already been declared");
|
|
SemanticTester::js("var Foo; import { Foo } from './foo.js';")
|
|
.has_error("already been declared");
|
|
// Import + let
|
|
SemanticTester::js("import { Foo } from './foo.js'; let Foo;")
|
|
.has_error("already been declared");
|
|
// Import + const
|
|
SemanticTester::js("import { Foo } from './foo.js'; const Foo = 1;")
|
|
.has_error("already been declared");
|
|
// Import + class
|
|
SemanticTester::js("import { Foo } from './foo.js'; class Foo {}")
|
|
.has_error("already been declared");
|
|
// Import + function
|
|
SemanticTester::js("import { Foo } from './foo.js'; function Foo() {}")
|
|
.has_error("already been declared");
|
|
// TypeScript should NOT report this error (declaration merging)
|
|
SemanticTester::ts("import { Foo } from './foo.js'; var Foo;").has_root_symbol("Foo").test();
|
|
}
|
|
|
|
#[test]
|
|
fn test_implicit_arguments_unresolved_references() {
|
|
let tester = SemanticTester::ts(
|
|
"const arguments = []; function f(value = arguments) { return () => arguments; }",
|
|
);
|
|
let semantic = tester.build();
|
|
let scoping = semantic.scoping();
|
|
let references = scoping.root_unresolved_references().get("arguments").unwrap();
|
|
assert_eq!(references.len(), 2);
|
|
assert_ne!(references[0], references[1]);
|
|
for &reference_id in references {
|
|
assert!(scoping.get_reference(reference_id).symbol_id().is_none());
|
|
}
|
|
}
|