use oxc_parser::Parser;
use oxc_span::SourceType;
use vize_l0::{Allocator, SourceRoot, Span, String};
use vize_l2::file::{DeclarationKind, FileIssueKind, InitializerKind, Namespace};
use vize_l2::lang::js::{FileProducer, ProgramInput, ProgramScope};
#[test]
fn actual_declaration_sites_keep_container_index_and_full_file_spans() {
let arena = Allocator::default();
let source = "<!--é--><script>export const 日本語 = 1; let tip = 'x';</script>";
let start = source.find("export").unwrap();
let end = source.find("</script>").unwrap();
let block = SourceRoot::new(source)
.unwrap()
.block(source.get(start..end).unwrap(), start as u32)
.unwrap();
let parsed = Parser::new(&arena, block.source(), SourceType::mjs()).parse_observed();
assert!(parsed.diagnostics().is_empty());
let input = ProgramInput::checked(parsed.admitted().unwrap(), block, 3).unwrap();
let mut producer = FileProducer::new(&arena, source).unwrap();
let unit = producer.program(input, ProgramScope::Module).unwrap();
let file = producer.finish().unwrap();
assert!(file.is_complete());
assert_eq!(unit.index(), 3);
assert_eq!(file.units()[0].span, block.span());
let bindings: Vec<_> = file.bindings().collect();
assert_eq!(bindings.len(), 2);
let declaration = bindings[0].declaration().unwrap();
assert_eq!(declaration.id.index(), 0);
assert_eq!(declaration.name, "日本語");
assert_eq!(
declaration.span.start as usize,
source.find("日本語").unwrap()
);
assert_eq!(declaration.kind, DeclarationKind::Const);
assert_eq!(declaration.initializer, InitializerKind::PrimitiveLiteral);
assert_eq!(file.exports()[0].local, Some(declaration.id));
assert_eq!(file.exports()[0].unit, unit);
assert!(file.references().is_empty());
}
#[test]
fn type_only_imports_occupy_the_type_namespace_without_runtime_binding() {
let arena = Allocator::default();
let source = "import type {T} from 'types'; import make, {value as local} from 'dep';";
let source_type = SourceType::ts().with_module(true);
let parsed = Parser::new(&arena, source, source_type).parse_observed();
assert!(parsed.diagnostics().is_empty());
let block = SourceRoot::new(source).unwrap().whole_block();
let mut producer = FileProducer::new(&arena, source).unwrap();
producer
.program(
ProgramInput::checked(parsed.admitted().unwrap(), block, 0).unwrap(),
ProgramScope::Module,
)
.unwrap();
let file = producer.finish().unwrap();
let scope = file.units()[0].scope;
assert!(file.is_complete());
assert!(file.lookup(scope, "T", Namespace::Value).is_none());
assert_eq!(
file.lookup(scope, "T", Namespace::Type)
.unwrap()
.id()
.index(),
0
);
let local = file.lookup(scope, "local", Namespace::Value).unwrap();
assert_eq!(
local.declaration().unwrap().imported_name.as_deref(),
Some("value")
);
assert_eq!(
local.declaration().unwrap().import_source.as_deref(),
Some("dep")
);
assert_eq!(file.imports().len(), 2);
}
#[test]
fn borrowed_handles_distinguish_files_even_when_numeric_ids_match() {
let arena = Allocator::default();
let source = "const value = 1;";
let parsed = Parser::new(&arena, source, SourceType::mjs()).parse_observed();
let block = SourceRoot::new(source).unwrap().whole_block();
let build = || {
let mut producer = FileProducer::new(&arena, source).unwrap();
producer
.program(
ProgramInput::checked(parsed.admitted().unwrap(), block, 0).unwrap(),
ProgramScope::Module,
)
.unwrap();
producer.finish().unwrap()
};
let first = build();
let second = build();
let a = first.bindings().next().unwrap();
let b = second.bindings().next().unwrap();
assert_eq!(a.id(), b.id());
assert!(a.same_owner(a));
assert!(!a.same_owner(b));
assert!(core::ptr::eq(a.file(), &first));
}
#[test]
fn whole_program_admission_rejects_foreign_source_and_builder_ownership() {
let arena = Allocator::default();
let source = String::from("const value = 1;");
let copy = source.clone();
let parsed = Parser::new(&arena, &source, SourceType::mjs()).parse_observed();
let foreign = SourceRoot::new(©).unwrap().whole_block();
assert!(
matches!(ProgramInput::checked(parsed.admitted().unwrap(), foreign, 0),
Err(error) if error.kind == FileIssueKind::InvalidSource)
);
let block = SourceRoot::new(&source).unwrap().whole_block();
let mut producer = FileProducer::new(&arena, ©).unwrap();
assert_eq!(
producer
.program(
ProgramInput::checked(parsed.admitted().unwrap(), block, 0).unwrap(),
ProgramScope::Module
)
.unwrap_err()
.kind,
FileIssueKind::InvalidSource
);
}
#[test]
fn actual_nested_unit_shadowing_preserves_distinct_declaration_sites() {
let arena = Allocator::default();
let source = "const value = 1; const value = 2;";
let split = source.find(" const").unwrap() + 1;
let root = SourceRoot::new(source).unwrap();
let first = root.block(source.get(..split).unwrap(), 0).unwrap();
let second = root
.block(source.get(split..).unwrap(), split as u32)
.unwrap();
let a = Parser::new(&arena, first.source(), SourceType::mjs()).parse_observed();
let b = Parser::new(&arena, second.source(), SourceType::mjs()).parse_observed();
let mut producer = FileProducer::new(&arena, source).unwrap();
producer
.program(
ProgramInput::checked(a.admitted().unwrap(), first, 0).unwrap(),
ProgramScope::Module,
)
.unwrap();
producer
.program(
ProgramInput::checked(b.admitted().unwrap(), second, 2).unwrap(),
ProgramScope::Nested,
)
.unwrap();
let file = producer.finish().unwrap();
let outer = file.units()[0].scope;
let inner = file.units()[1].scope;
assert!(file.is_complete());
assert_ne!(outer, inner);
assert_eq!(file.scopes()[1].parent, Some(outer));
assert_ne!(
file.lookup(outer, "value", Namespace::Value).unwrap().id(),
file.lookup(inner, "value", Namespace::Value).unwrap().id()
);
}
#[test]
fn unsupported_subtrees_never_produce_false_complete_script_units() {
let arena = Allocator::default();
for source in [
"const value = () => 1;",
"const {value} = obj;",
"function f() { function nested() {} }",
"class C {}",
"const value: number | string = 1;",
] {
let parsed =
Parser::new(&arena, source, SourceType::ts().with_module(true)).parse_observed();
assert!(parsed.diagnostics().is_empty(), "{source}");
let mut producer = FileProducer::new(&arena, source).unwrap();
producer
.program(
ProgramInput::checked(
parsed.admitted().unwrap(),
SourceRoot::new(source).unwrap().whole_block(),
0,
)
.unwrap(),
ProgramScope::Module,
)
.unwrap();
let file = producer.finish().unwrap();
assert!(!file.is_complete(), "{source}");
assert!(
file.issues()
.iter()
.any(|issue| issue.kind == FileIssueKind::UnsupportedSyntax)
);
assert!(file.references().is_empty());
}
}
#[test]
fn duplicate_container_indices_are_rejected_without_duplicate_bindings() {
let arena = Allocator::default();
let source = "const value = 1;";
let parsed = Parser::new(&arena, source, SourceType::mjs()).parse_observed();
let block = SourceRoot::new(source).unwrap().whole_block();
let mut producer = FileProducer::new(&arena, source).unwrap();
producer
.program(
ProgramInput::checked(parsed.admitted().unwrap(), block, 7).unwrap(),
ProgramScope::Module,
)
.unwrap();
assert_eq!(
producer
.program(
ProgramInput::checked(parsed.admitted().unwrap(), block, 7).unwrap(),
ProgramScope::Module
)
.unwrap_err()
.kind,
FileIssueKind::DuplicateUnit
);
let file = producer.finish().unwrap();
assert!(!file.is_complete());
assert_eq!(file.units().len(), 1);
assert_eq!(file.bindings().count(), 1);
assert_eq!(file.issues()[0].span, Span::new(0, source.len() as u32));
}
#[test]
fn declaration_names_come_from_real_ast_semantics_with_authored_escape_ranges() {
let arena = Allocator::default();
let source = "const \\u0076alue = 1;";
let parsed = Parser::new(&arena, source, SourceType::mjs()).parse_observed();
assert!(parsed.diagnostics().is_empty());
let mut producer = FileProducer::new(&arena, source).unwrap();
producer
.program(
ProgramInput::checked(
parsed.admitted().unwrap(),
SourceRoot::new(source).unwrap().whole_block(),
0,
)
.unwrap(),
ProgramScope::Module,
)
.unwrap();
let file = producer.finish().unwrap();
let binding = file.bindings().next().unwrap();
assert!(file.is_complete());
assert_eq!(binding.declaration().unwrap().name, "value");
assert_eq!(binding.declaration().unwrap().span, Span::new(6, 16));
}
#[test]
fn declaration_profiles_outside_the_admitted_module_family_stay_incomplete() {
let arena = Allocator::default();
let source = "const value = 1;";
for source_type in [
SourceType::cjs(),
SourceType::ts()
.with_module(true)
.with_typescript_definition(true),
] {
let parsed = Parser::new(&arena, source, source_type).parse_observed();
let mut producer = FileProducer::new(&arena, source).unwrap();
producer
.program(
ProgramInput::checked(
parsed.admitted().unwrap(),
SourceRoot::new(source).unwrap().whole_block(),
0,
)
.unwrap(),
ProgramScope::Module,
)
.unwrap();
let file = producer.finish().unwrap();
assert!(!file.is_complete());
assert_eq!(file.bindings().count(), 0);
assert_eq!(file.issues()[0].kind, FileIssueKind::InvalidProfile);
}
}