use crate::binder::BinderOptions;
use crate::binder::BinderState;
use crate::binder::state::{BinderStateScopeInputs, DeclarationArenaMap};
use crate::binder::{
FlowNodeArena, FlowNodeId, Scope, ScopeId, SymbolArena, SymbolId, SymbolTable,
};
#[cfg(not(target_arch = "wasm32"))]
use crate::config::resolve_default_lib_files;
use crate::emitter::ScriptTarget;
use crate::lib_loader;
use crate::parser::NodeIndex;
use crate::parser::node::NodeArena;
use crate::parser::{ParseDiagnostic, ParserState};
use anyhow::{Context, Result, bail};
#[cfg(not(target_arch = "wasm32"))]
use rayon::prelude::{
IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator,
};
use rustc_hash::{FxHashMap, FxHashSet};
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Once;
use tsz_common::interner::{Atom, Interner};
type ModuleExportEntry = FxHashMap<String, (String, Option<String>)>;
type Reexports = FxHashMap<String, ModuleExportEntry>;
#[cfg(target_arch = "wasm32")]
fn resolve_default_lib_files(_target: ScriptTarget) -> anyhow::Result<Vec<PathBuf>> {
Ok(Vec::new())
}
#[cfg(not(target_arch = "wasm32"))]
static RAYON_POOL_INIT: Once = Once::new();
#[cfg(not(target_arch = "wasm32"))]
pub fn ensure_rayon_global_pool() {
RAYON_POOL_INIT.call_once(|| {
let _ = rayon::ThreadPoolBuilder::new()
.stack_size(8 * 1024 * 1024)
.build_global();
});
}
#[cfg(target_arch = "wasm32")]
pub fn ensure_rayon_global_pool() {}
#[cfg(target_arch = "wasm32")]
macro_rules! maybe_parallel_iter {
($iter:expr) => {
$iter.iter()
};
}
#[cfg(not(target_arch = "wasm32"))]
macro_rules! maybe_parallel_iter {
($iter:expr) => {
$iter.par_iter()
};
}
#[cfg(target_arch = "wasm32")]
macro_rules! maybe_parallel_into {
($iter:expr) => {
$iter.into_iter()
};
}
#[cfg(not(target_arch = "wasm32"))]
macro_rules! maybe_parallel_into {
($iter:expr) => {
$iter.into_par_iter()
};
}
pub struct ParseResult {
pub file_name: String,
pub source_file: NodeIndex,
pub arena: NodeArena,
pub parse_diagnostics: Vec<ParseDiagnostic>,
}
pub fn parse_files_parallel(files: Vec<(String, String)>) -> Vec<ParseResult> {
#[cfg(not(target_arch = "wasm32"))]
ensure_rayon_global_pool();
maybe_parallel_into!(files)
.map(|(file_name, source_text)| {
let mut parser = ParserState::new(file_name.clone(), source_text);
let source_file = parser.parse_source_file();
let (arena, parse_diagnostics) = parser.into_parts();
ParseResult {
file_name,
source_file,
arena,
parse_diagnostics,
}
})
.collect()
}
pub fn parse_file_single(file_name: String, source_text: String) -> ParseResult {
let mut parser = ParserState::new(file_name.clone(), source_text);
let source_file = parser.parse_source_file();
let (arena, parse_diagnostics) = parser.into_parts();
ParseResult {
file_name,
source_file,
arena,
parse_diagnostics,
}
}
#[derive(Debug, Clone)]
pub struct ParallelStats {
pub file_count: usize,
pub total_bytes: usize,
pub total_nodes: usize,
pub error_count: usize,
}
pub struct BindResult {
pub file_name: String,
pub source_file: NodeIndex,
pub arena: Arc<NodeArena>,
pub symbols: SymbolArena,
pub file_locals: SymbolTable,
pub declared_modules: FxHashSet<String>,
pub module_exports: FxHashMap<String, SymbolTable>,
pub node_symbols: FxHashMap<u32, SymbolId>,
pub symbol_arenas: FxHashMap<SymbolId, Arc<NodeArena>>,
pub declaration_arenas: DeclarationArenaMap,
pub scopes: Vec<Scope>,
pub node_scope_ids: FxHashMap<u32, ScopeId>,
pub parse_diagnostics: Vec<ParseDiagnostic>,
pub shorthand_ambient_modules: FxHashSet<String>,
pub global_augmentations: FxHashMap<String, Vec<crate::binder::GlobalAugmentation>>,
pub module_augmentations: FxHashMap<String, Vec<crate::binder::ModuleAugmentation>>,
pub reexports: Reexports,
pub wildcard_reexports: FxHashMap<String, Vec<String>>,
pub lib_binders: Vec<Arc<BinderState>>,
pub lib_symbol_ids: FxHashSet<SymbolId>,
pub lib_symbol_reverse_remap: FxHashMap<SymbolId, (usize, SymbolId)>,
pub flow_nodes: FlowNodeArena,
pub node_flow: FxHashMap<u32, FlowNodeId>,
pub switch_clause_to_switch: FxHashMap<u32, NodeIndex>,
pub is_external_module: bool,
pub expando_properties: FxHashMap<String, FxHashSet<String>>,
}
pub fn parse_and_bind_parallel(files: Vec<(String, String)>) -> Vec<BindResult> {
#[cfg(not(target_arch = "wasm32"))]
ensure_rayon_global_pool();
maybe_parallel_into!(files)
.map(|(file_name, source_text)| {
if file_name.ends_with(".json") {
let arena = NodeArena::new();
let source_file = NodeIndex::NONE;
let parse_diagnostics = Vec::new();
let binder = BinderState::new();
return BindResult {
file_name,
source_file,
arena: Arc::new(arena),
symbols: binder.symbols,
file_locals: binder.file_locals,
declared_modules: binder.declared_modules,
module_exports: binder.module_exports,
node_symbols: binder.node_symbols,
symbol_arenas: binder.symbol_arenas,
declaration_arenas: binder.declaration_arenas,
scopes: binder.scopes,
node_scope_ids: binder.node_scope_ids,
parse_diagnostics,
shorthand_ambient_modules: binder.shorthand_ambient_modules,
global_augmentations: binder.global_augmentations,
module_augmentations: binder.module_augmentations,
reexports: binder.reexports,
wildcard_reexports: binder.wildcard_reexports,
lib_binders: Vec::new(),
lib_symbol_ids: binder.lib_symbol_ids,
lib_symbol_reverse_remap: binder.lib_symbol_reverse_remap,
flow_nodes: binder.flow_nodes,
node_flow: binder.node_flow,
switch_clause_to_switch: binder.switch_clause_to_switch,
is_external_module: false,
expando_properties: FxHashMap::default(),
};
}
let mut parser = ParserState::new(file_name.clone(), source_text);
let source_file = parser.parse_source_file();
let (arena, parse_diagnostics) = parser.into_parts();
let mut binder = BinderState::new();
binder.set_debug_file(&file_name);
binder.bind_source_file(&arena, source_file);
BindResult {
file_name,
source_file,
arena: Arc::new(arena),
symbols: binder.symbols,
file_locals: binder.file_locals,
declared_modules: binder.declared_modules,
module_exports: binder.module_exports,
node_symbols: binder.node_symbols,
symbol_arenas: binder.symbol_arenas,
declaration_arenas: binder.declaration_arenas,
scopes: binder.scopes,
node_scope_ids: binder.node_scope_ids,
parse_diagnostics,
shorthand_ambient_modules: binder.shorthand_ambient_modules,
global_augmentations: binder.global_augmentations,
module_augmentations: binder.module_augmentations,
reexports: binder.reexports,
wildcard_reexports: binder.wildcard_reexports,
lib_binders: Vec::new(), lib_symbol_ids: binder.lib_symbol_ids,
lib_symbol_reverse_remap: binder.lib_symbol_reverse_remap,
flow_nodes: binder.flow_nodes,
node_flow: binder.node_flow,
switch_clause_to_switch: std::mem::take(&mut binder.switch_clause_to_switch),
is_external_module: binder.is_external_module,
expando_properties: std::mem::take(&mut binder.expando_properties),
}
})
.collect()
}
pub fn parse_and_bind_single(file_name: String, source_text: String) -> BindResult {
let mut parser = ParserState::new(file_name.clone(), source_text);
let source_file = parser.parse_source_file();
let (arena, parse_diagnostics) = parser.into_parts();
let mut binder = BinderState::new();
binder.set_debug_file(&file_name);
binder.bind_source_file(&arena, source_file);
BindResult {
file_name,
source_file,
arena: Arc::new(arena),
symbols: binder.symbols,
file_locals: binder.file_locals,
declared_modules: binder.declared_modules,
module_exports: binder.module_exports,
node_symbols: binder.node_symbols,
symbol_arenas: binder.symbol_arenas,
declaration_arenas: binder.declaration_arenas,
scopes: binder.scopes,
node_scope_ids: binder.node_scope_ids,
parse_diagnostics,
shorthand_ambient_modules: binder.shorthand_ambient_modules,
global_augmentations: binder.global_augmentations,
module_augmentations: binder.module_augmentations,
reexports: binder.reexports,
wildcard_reexports: binder.wildcard_reexports,
lib_binders: Vec::new(), lib_symbol_ids: binder.lib_symbol_ids,
lib_symbol_reverse_remap: binder.lib_symbol_reverse_remap,
flow_nodes: binder.flow_nodes,
node_flow: binder.node_flow,
switch_clause_to_switch: std::mem::take(&mut binder.switch_clause_to_switch),
is_external_module: binder.is_external_module,
expando_properties: std::mem::take(&mut binder.expando_properties),
}
}
#[derive(Debug, Clone)]
pub struct BindStats {
pub file_count: usize,
pub total_nodes: usize,
pub total_symbols: usize,
pub parse_error_count: usize,
}
pub fn parse_and_bind_with_stats(files: Vec<(String, String)>) -> (Vec<BindResult>, BindStats) {
let file_count = files.len();
let results = parse_and_bind_parallel(files);
let total_nodes: usize = results.iter().map(|r| r.arena.len()).sum();
let total_symbols: usize = results.iter().map(|r| r.symbols.len()).sum();
let parse_error_count: usize = results.iter().map(|r| r.parse_diagnostics.len()).sum();
let stats = BindStats {
file_count,
total_nodes,
total_symbols,
parse_error_count,
};
(results, stats)
}
pub fn load_lib_files_for_binding(lib_files: &[&Path]) -> Vec<Arc<lib_loader::LibFile>> {
use crate::parser::ParserState;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
if lib_files.is_empty() {
return Vec::new();
}
let files_to_load: Vec<_> = lib_files
.iter()
.filter_map(|p| {
let path = p.to_path_buf();
path.exists().then_some(path)
})
.collect();
files_to_load
.into_par_iter()
.filter_map(|lib_path| {
let source_text = std::fs::read_to_string(&lib_path).ok()?;
let file_name = lib_path.to_string_lossy().to_string();
let mut lib_parser = ParserState::new(file_name.clone(), source_text);
let source_file_idx = lib_parser.parse_source_file();
if !lib_parser.get_diagnostics().is_empty() {
return None;
}
let mut lib_binder = BinderState::new();
lib_binder.bind_source_file(lib_parser.get_arena(), source_file_idx);
let arena = Arc::new(lib_parser.into_arena());
let binder = Arc::new(lib_binder);
Some(Arc::new(lib_loader::LibFile::new(file_name, arena, binder)))
})
.collect()
}
pub fn load_lib_files_for_binding_strict(
lib_files: &[&Path],
) -> Result<Vec<Arc<lib_loader::LibFile>>> {
use crate::parser::ParserState;
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
if lib_files.is_empty() {
return Ok(Vec::new());
}
lib_files
.par_iter()
.map(|path| {
let lib_path = path.to_path_buf();
if !lib_path.exists() {
bail!("lib file not found on disk: {}", lib_path.display());
}
let source_text = std::fs::read_to_string(&lib_path)
.with_context(|| format!("failed to read lib file {}", lib_path.display()))?;
let file_name = lib_path.to_string_lossy().to_string();
let mut lib_parser = ParserState::new(file_name.clone(), source_text);
let source_file_idx = lib_parser.parse_source_file();
let diagnostics = lib_parser.get_diagnostics();
if !diagnostics.is_empty() {
let first = &diagnostics[0];
bail!(
"failed to parse lib file {} ({}:{}): {}",
lib_path.display(),
first.start,
first.length,
first.message
);
}
let mut lib_binder = BinderState::new();
lib_binder.bind_source_file(lib_parser.get_arena(), source_file_idx);
let arena = Arc::new(lib_parser.into_arena());
let binder = Arc::new(lib_binder);
Ok(Arc::new(lib_loader::LibFile::new(file_name, arena, binder)))
})
.collect()
}
pub fn parse_and_bind_parallel_with_lib_files(
files: Vec<(String, String)>,
lib_files: &[&Path],
) -> Vec<BindResult> {
let lib_contexts = load_lib_files_for_binding_strict(lib_files)
.unwrap_or_else(|err| panic!("failed to load lib files from disk: {err}"));
parse_and_bind_parallel_with_libs(files, &lib_contexts)
}
pub fn parse_and_bind_parallel_with_libs(
files: Vec<(String, String)>,
lib_files: &[Arc<lib_loader::LibFile>],
) -> Vec<BindResult> {
if files.len() <= 1 {
return files
.into_iter()
.map(|(file_name, source_text)| bind_file_with_libs(file_name, source_text, lib_files))
.collect();
}
#[cfg(not(target_arch = "wasm32"))]
ensure_rayon_global_pool();
maybe_parallel_into!(files)
.map(|(file_name, source_text)| bind_file_with_libs(file_name, source_text, lib_files))
.collect()
}
fn bind_file_with_libs(
file_name: String,
source_text: String,
lib_files: &[Arc<lib_loader::LibFile>],
) -> BindResult {
if file_name.ends_with(".json") {
let arena = NodeArena::new();
let source_file = NodeIndex::NONE;
let parse_diagnostics = Vec::new();
let binder = BinderState::new();
let lib_binders = binder.lib_binders.clone();
return BindResult {
file_name,
source_file,
arena: Arc::new(arena),
symbols: binder.symbols,
file_locals: binder.file_locals,
declared_modules: binder.declared_modules,
module_exports: binder.module_exports,
node_symbols: binder.node_symbols,
symbol_arenas: binder.symbol_arenas,
declaration_arenas: binder.declaration_arenas,
scopes: binder.scopes,
node_scope_ids: binder.node_scope_ids,
parse_diagnostics,
shorthand_ambient_modules: binder.shorthand_ambient_modules,
global_augmentations: binder.global_augmentations,
module_augmentations: binder.module_augmentations,
reexports: binder.reexports,
wildcard_reexports: binder.wildcard_reexports,
lib_binders,
lib_symbol_ids: binder.lib_symbol_ids,
lib_symbol_reverse_remap: binder.lib_symbol_reverse_remap,
flow_nodes: binder.flow_nodes,
node_flow: binder.node_flow,
switch_clause_to_switch: binder.switch_clause_to_switch,
is_external_module: false,
expando_properties: FxHashMap::default(),
};
}
let mut parser = ParserState::new(file_name.clone(), source_text);
let source_file = parser.parse_source_file();
let (arena, parse_diagnostics) = parser.into_parts();
let mut binder = BinderState::new();
binder.set_debug_file(&file_name);
if !lib_files.is_empty() {
binder.merge_lib_symbols(lib_files);
}
binder.bind_source_file(&arena, source_file);
let lib_binders = binder.lib_binders.clone();
BindResult {
file_name,
source_file,
arena: Arc::new(arena),
symbols: binder.symbols,
file_locals: binder.file_locals,
declared_modules: binder.declared_modules,
module_exports: binder.module_exports,
node_symbols: binder.node_symbols,
symbol_arenas: binder.symbol_arenas,
declaration_arenas: binder.declaration_arenas,
scopes: binder.scopes,
node_scope_ids: binder.node_scope_ids,
parse_diagnostics,
shorthand_ambient_modules: binder.shorthand_ambient_modules,
global_augmentations: binder.global_augmentations,
module_augmentations: binder.module_augmentations,
reexports: binder.reexports,
wildcard_reexports: binder.wildcard_reexports,
lib_binders,
lib_symbol_ids: binder.lib_symbol_ids,
lib_symbol_reverse_remap: binder.lib_symbol_reverse_remap,
flow_nodes: binder.flow_nodes,
node_flow: binder.node_flow,
switch_clause_to_switch: std::mem::take(&mut binder.switch_clause_to_switch),
is_external_module: binder.is_external_module,
expando_properties: std::mem::take(&mut binder.expando_properties),
}
}
pub struct BoundFile {
pub file_name: String,
pub source_file: NodeIndex,
pub arena: Arc<NodeArena>,
pub node_symbols: FxHashMap<u32, SymbolId>,
pub scopes: Vec<Scope>,
pub node_scope_ids: FxHashMap<u32, ScopeId>,
pub parse_diagnostics: Vec<ParseDiagnostic>,
pub global_augmentations: FxHashMap<String, Vec<crate::binder::GlobalAugmentation>>,
pub module_augmentations: FxHashMap<String, Vec<crate::binder::ModuleAugmentation>>,
pub flow_nodes: FlowNodeArena,
pub node_flow: FxHashMap<u32, FlowNodeId>,
pub switch_clause_to_switch: FxHashMap<u32, NodeIndex>,
pub is_external_module: bool,
pub expando_properties: FxHashMap<String, FxHashSet<String>>,
}
use tsz_solver::TypeInterner;
pub struct MergedProgram {
pub files: Vec<BoundFile>,
pub symbols: SymbolArena,
pub symbol_arenas: FxHashMap<SymbolId, Arc<NodeArena>>,
pub declaration_arenas: DeclarationArenaMap,
pub globals: SymbolTable,
pub file_locals: Vec<SymbolTable>,
pub declared_modules: FxHashSet<String>,
pub shorthand_ambient_modules: FxHashSet<String>,
pub module_exports: FxHashMap<String, SymbolTable>,
pub reexports: Reexports,
pub wildcard_reexports: FxHashMap<String, Vec<String>>,
pub lib_binders: Vec<Arc<BinderState>>,
pub lib_symbol_ids: FxHashSet<SymbolId>,
pub type_interner: TypeInterner,
}
const fn can_merge_symbols_cross_file(existing_flags: u32, new_flags: u32) -> bool {
use crate::binder::symbol_flags;
if (existing_flags & symbol_flags::INTERFACE) != 0 && (new_flags & symbol_flags::INTERFACE) != 0
{
return true;
}
if ((existing_flags & symbol_flags::CLASS) != 0 && (new_flags & symbol_flags::INTERFACE) != 0)
|| ((existing_flags & symbol_flags::INTERFACE) != 0
&& (new_flags & symbol_flags::CLASS) != 0)
{
return true;
}
if ((existing_flags & symbol_flags::INTERFACE) != 0
&& (new_flags & symbol_flags::VARIABLE) != 0)
|| ((existing_flags & symbol_flags::VARIABLE) != 0
&& (new_flags & symbol_flags::INTERFACE) != 0)
{
return true;
}
if ((existing_flags & symbol_flags::INTERFACE) != 0
&& (new_flags & symbol_flags::FUNCTION) != 0)
|| ((existing_flags & symbol_flags::FUNCTION) != 0
&& (new_flags & symbol_flags::INTERFACE) != 0)
{
return true;
}
if (existing_flags & symbol_flags::MODULE) != 0 && (new_flags & symbol_flags::MODULE) != 0 {
return true;
}
if (existing_flags & symbol_flags::VARIABLE) != 0 && (new_flags & symbol_flags::VARIABLE) != 0 {
return true;
}
if (existing_flags & symbol_flags::CLASS) != 0 && (new_flags & symbol_flags::CLASS) != 0 {
return true;
}
if ((existing_flags & symbol_flags::CLASS) != 0 && (new_flags & symbol_flags::TYPE_ALIAS) != 0)
|| ((existing_flags & symbol_flags::TYPE_ALIAS) != 0
&& (new_flags & symbol_flags::CLASS) != 0)
{
return true;
}
if (existing_flags & symbol_flags::TYPE_ALIAS) != 0
&& (new_flags & symbol_flags::TYPE_ALIAS) != 0
{
return true;
}
if ((existing_flags & symbol_flags::CLASS) != 0 && (new_flags & symbol_flags::VARIABLE) != 0)
|| ((existing_flags & symbol_flags::VARIABLE) != 0
&& (new_flags & symbol_flags::CLASS) != 0)
{
return true;
}
if ((existing_flags & symbol_flags::TYPE_ALIAS) != 0
&& (new_flags & symbol_flags::VARIABLE) != 0)
|| ((existing_flags & symbol_flags::VARIABLE) != 0
&& (new_flags & symbol_flags::TYPE_ALIAS) != 0)
{
return true;
}
if (existing_flags & symbol_flags::MODULE) != 0
&& (new_flags
& (symbol_flags::CLASS
| symbol_flags::FUNCTION
| symbol_flags::ENUM
| symbol_flags::VARIABLE))
!= 0
{
return true;
}
if (new_flags & symbol_flags::MODULE) != 0
&& (existing_flags
& (symbol_flags::CLASS
| symbol_flags::FUNCTION
| symbol_flags::ENUM
| symbol_flags::VARIABLE))
!= 0
{
return true;
}
if (existing_flags & symbol_flags::ENUM) != 0 && (new_flags & symbol_flags::ENUM) != 0 {
return true;
}
false
}
fn append_unique_declarations(existing: &mut Vec<NodeIndex>, incoming: &[NodeIndex]) {
existing.extend_from_slice(incoming);
}
pub fn merge_bind_results(results: Vec<BindResult>) -> MergedProgram {
let refs: Vec<&BindResult> = results.iter().collect();
merge_bind_results_ref(&refs)
}
pub fn merge_bind_results_ref(results: &[&BindResult]) -> MergedProgram {
let mut lib_binders: Vec<Arc<BinderState>> = Vec::new();
let mut lib_binder_set: FxHashSet<usize> = FxHashSet::default();
for result in results {
for lib_binder in &result.lib_binders {
let binder_addr = Arc::as_ptr(lib_binder) as usize;
if lib_binder_set.insert(binder_addr) {
lib_binders.push(Arc::clone(lib_binder));
}
}
}
let lib_symbol_count: usize = lib_binders.iter().map(|b| b.symbols.len()).sum();
let user_symbol_count: usize = results.iter().map(|r| r.symbols.len()).sum();
let total_symbols = lib_symbol_count + user_symbol_count;
let mut global_symbols = SymbolArena::with_capacity(total_symbols);
let mut symbol_arenas = FxHashMap::default();
let mut declaration_arenas: DeclarationArenaMap = FxHashMap::default();
let mut globals = SymbolTable::new();
let mut files = Vec::with_capacity(results.len());
let mut file_locals_list = Vec::with_capacity(results.len());
let mut declared_modules = FxHashSet::default();
let mut shorthand_ambient_modules = FxHashSet::default();
let mut module_exports: FxHashMap<String, SymbolTable> = FxHashMap::default();
let mut reexports: Reexports = FxHashMap::default();
let mut wildcard_reexports: FxHashMap<String, Vec<String>> = FxHashMap::default();
let mut global_lib_symbol_ids: FxHashSet<SymbolId> = FxHashSet::default();
let mut name_interner = Interner::new();
let mut merged_symbols: FxHashMap<Atom, SymbolId> = FxHashMap::default();
let mut lib_symbol_remap: FxHashMap<(usize, SymbolId), SymbolId> = FxHashMap::default();
for lib_binder in &lib_binders {
let lib_binder_ptr = Arc::as_ptr(lib_binder) as usize;
let top_level_ids: FxHashSet<SymbolId> =
lib_binder.file_locals.iter().map(|(_, id)| *id).collect();
let mut decl_arenas_by_sym: FxHashMap<SymbolId, Vec<(NodeIndex, Arc<NodeArena>)>> =
FxHashMap::default();
for (&(sym_id, decl_idx), arenas) in &lib_binder.declaration_arenas {
for arena in arenas {
decl_arenas_by_sym
.entry(sym_id)
.or_default()
.push((decl_idx, Arc::clone(arena)));
}
}
for i in 0..lib_binder.symbols.len() {
let local_id = SymbolId(i as u32);
if let Some(lib_sym) = lib_binder.symbols.get(local_id) {
let is_top_level = top_level_ids.contains(&local_id);
let global_id = if is_top_level {
let name_atom = name_interner.intern(&lib_sym.escaped_name);
if let Some(&existing_id) = merged_symbols.get(&name_atom) {
if let Some(existing_sym) = global_symbols.get(existing_id) {
if can_merge_symbols_cross_file(existing_sym.flags, lib_sym.flags) {
if let Some(existing_mut) = global_symbols.get_mut(existing_id) {
existing_mut.flags |= lib_sym.flags;
append_unique_declarations(
&mut existing_mut.declarations,
&lib_sym.declarations,
);
}
existing_id
} else {
let new_id = global_symbols.alloc_from(lib_sym);
merged_symbols.insert(name_atom, new_id);
new_id
}
} else {
let new_id = global_symbols.alloc_from(lib_sym);
merged_symbols.insert(name_atom, new_id);
new_id
}
} else {
let new_id = global_symbols.alloc_from(lib_sym);
merged_symbols.insert(name_atom, new_id);
new_id
}
} else {
global_symbols.alloc_from(lib_sym)
};
lib_symbol_remap.insert((lib_binder_ptr, local_id), global_id);
if let Some(lib_arena) = lib_binder.symbol_arenas.get(&local_id) {
symbol_arenas.insert(global_id, Arc::clone(lib_arena));
}
if let Some(entries) = decl_arenas_by_sym.get(&local_id) {
for (decl_idx, arena) in entries {
declaration_arenas
.entry((global_id, *decl_idx))
.or_default()
.push(Arc::clone(arena));
}
}
}
}
}
for lib_binder in &lib_binders {
let lib_binder_ptr = Arc::as_ptr(lib_binder) as usize;
for i in 0..lib_binder.symbols.len() {
let local_id = SymbolId(i as u32);
let Some(&global_id) = lib_symbol_remap.get(&(lib_binder_ptr, local_id)) else {
continue;
};
let Some(lib_sym) = lib_binder.symbols.get(local_id) else {
continue;
};
if !lib_sym.parent.is_none()
&& let Some(&new_parent) = lib_symbol_remap.get(&(lib_binder_ptr, lib_sym.parent))
&& let Some(sym) = global_symbols.get_mut(global_id)
{
sym.parent = new_parent;
}
if let Some(exports) = &lib_sym.exports
&& let Some(sym) = global_symbols.get_mut(global_id)
{
if sym.exports.is_none() {
sym.exports = Some(Box::new(SymbolTable::new()));
}
if let Some(existing) = sym.exports.as_mut() {
for (name, &export_id) in exports.iter() {
if let Some(&new_export_id) =
lib_symbol_remap.get(&(lib_binder_ptr, export_id))
{
existing.set(name.clone(), new_export_id);
}
}
}
}
if let Some(members) = &lib_sym.members
&& let Some(sym) = global_symbols.get_mut(global_id)
{
if sym.members.is_none() {
sym.members = Some(Box::new(SymbolTable::new()));
}
if let Some(existing) = sym.members.as_mut() {
for (name, &member_id) in members.iter() {
if let Some(&new_member_id) =
lib_symbol_remap.get(&(lib_binder_ptr, member_id))
{
existing.set(name.clone(), new_member_id);
}
}
}
}
}
}
let mut lib_name_to_global: FxHashMap<Atom, SymbolId> = FxHashMap::default();
for lib_binder in &lib_binders {
let lib_binder_ptr = Arc::as_ptr(lib_binder) as usize;
for (name, &local_id) in lib_binder.file_locals.iter() {
if let Some(&global_id) = lib_symbol_remap.get(&(lib_binder_ptr, local_id)) {
let name_atom = name_interner.intern(name);
lib_name_to_global.entry(name_atom).or_insert(global_id);
}
}
}
for (file_idx, result) in results.iter().enumerate() {
declared_modules.extend(result.declared_modules.iter().cloned());
shorthand_ambient_modules.extend(result.shorthand_ambient_modules.iter().cloned());
for (file_name, file_reexports) in &result.reexports {
let entry = reexports.entry(file_name.clone()).or_default();
for (export_name, mapping) in file_reexports {
entry.insert(export_name.clone(), mapping.clone());
}
}
for (file_name, source_modules) in &result.wildcard_reexports {
let entry = wildcard_reexports.entry(file_name.clone()).or_default();
if entry.len() + source_modules.len() <= 16 {
for source_module in source_modules {
if !entry.contains(source_module) {
entry.push(source_module.clone());
}
}
} else {
let mut seen: FxHashSet<String> = entry.iter().cloned().collect();
for source_module in source_modules {
if seen.insert(source_module.clone()) {
entry.push(source_module.clone());
}
}
}
}
let mut id_remap: FxHashMap<SymbolId, SymbolId> = FxHashMap::default();
for i in 0..result.symbols.len() {
let old_id = SymbolId(i as u32);
if let Some(sym) = result.symbols.get(old_id) {
if result.lib_symbol_ids.contains(&old_id) {
let mut resolved_global_id = None;
if let Some(&(binder_ptr, original_local_id)) =
result.lib_symbol_reverse_remap.get(&old_id)
&& let Some(&global_id) =
lib_symbol_remap.get(&(binder_ptr, original_local_id))
{
resolved_global_id = Some(global_id);
}
if resolved_global_id.is_none() {
let name_atom = name_interner.intern(&sym.escaped_name);
if let Some(&global_id) = merged_symbols.get(&name_atom) {
resolved_global_id = Some(global_id);
}
if resolved_global_id.is_none()
&& let Some(&global_id) = lib_name_to_global.get(&name_atom)
{
resolved_global_id = Some(global_id);
}
}
if let Some(global_id) = resolved_global_id {
if let Some(global_sym) = global_symbols.get_mut(global_id) {
let extra_flags = sym.flags & !global_sym.flags;
if extra_flags != 0 {
global_sym.flags |= extra_flags;
}
append_unique_declarations(
&mut global_sym.declarations,
&sym.declarations,
);
}
id_remap.insert(old_id, global_id);
continue;
}
let new_id = global_symbols.alloc(sym.flags, sym.escaped_name.clone());
symbol_arenas.insert(new_id, Arc::clone(&result.arena));
id_remap.insert(old_id, new_id);
continue;
}
let is_nested_symbol = !result.scopes.first().is_some_and(|root_scope| {
root_scope
.table
.get(&sym.escaped_name)
.is_some_and(|root_sym_id| root_sym_id == old_id)
});
let new_id = if !is_nested_symbol && !result.is_external_module {
let name_atom = name_interner.intern(&sym.escaped_name);
if let Some(&existing_id) = merged_symbols.get(&name_atom) {
if let Some(existing_sym) = global_symbols.get(existing_id) {
if can_merge_symbols_cross_file(existing_sym.flags, sym.flags) {
existing_id
} else {
let new_id =
global_symbols.alloc(sym.flags, sym.escaped_name.clone());
symbol_arenas.insert(new_id, Arc::clone(&result.arena));
merged_symbols.insert(name_atom, new_id);
new_id
}
} else {
let new_id = global_symbols.alloc(sym.flags, sym.escaped_name.clone());
symbol_arenas.insert(new_id, Arc::clone(&result.arena));
merged_symbols.insert(name_atom, new_id);
new_id
}
} else {
let new_id = global_symbols.alloc(sym.flags, sym.escaped_name.clone());
symbol_arenas.insert(new_id, Arc::clone(&result.arena));
merged_symbols.insert(name_atom, new_id);
new_id
}
} else {
let new_id = global_symbols.alloc(sym.flags, sym.escaped_name.clone());
symbol_arenas.insert(new_id, Arc::clone(&result.arena));
new_id
};
id_remap.insert(old_id, new_id);
}
}
for &old_lib_id in &result.lib_symbol_ids {
if let Some(&new_id) = id_remap.get(&old_lib_id) {
global_lib_symbol_ids.insert(new_id);
}
}
for (&old_sym_id, arena) in &result.symbol_arenas {
if let Some(&new_sym_id) = id_remap.get(&old_sym_id) {
symbol_arenas
.entry(new_sym_id)
.or_insert_with(|| Arc::clone(arena));
}
}
for (&(old_sym_id, decl_idx), arenas) in &result.declaration_arenas {
if let Some(&new_sym_id) = id_remap.get(&old_sym_id) {
let target = declaration_arenas
.entry((new_sym_id, decl_idx))
.or_default();
for arena in arenas {
target.push(Arc::clone(arena));
}
}
}
let mut exports = SymbolTable::new();
let mut export_equals_old: Option<SymbolId> = None;
for (name, &sym_id) in result.file_locals.iter() {
if name == "export=" {
export_equals_old = Some(sym_id);
}
if let Some(sym) = result.symbols.get(sym_id)
&& (sym.is_exported || name == "export=")
&& let Some(&remapped_id) = id_remap.get(&sym_id)
{
exports.set(name.clone(), remapped_id);
}
}
if let Some(old_export_equals_sym) = export_equals_old
&& let Some(target_symbol) = result.symbols.get(old_export_equals_sym)
{
if let Some(target_exports) = target_symbol.exports.as_ref() {
for (export_name, old_sym_id) in target_exports.iter() {
if let Some(&remapped_id) = id_remap.get(old_sym_id) {
exports.set(export_name.clone(), remapped_id);
}
}
}
if let Some(target_members) = target_symbol.members.as_ref() {
for (member_name, old_sym_id) in target_members.iter() {
if let Some(&remapped_id) = id_remap.get(old_sym_id) {
exports.set(member_name.clone(), remapped_id);
}
}
}
}
let mut default_export_old: Option<SymbolId> = None;
if let Some(root_node) = result.arena.get(result.source_file)
&& let Some(source) = result.arena.get_source_file(root_node)
{
for &stmt_idx in &source.statements.nodes {
let Some(stmt_node) = result.arena.get(stmt_idx) else {
continue;
};
if stmt_node.kind != syntax_kind_ext::EXPORT_DECLARATION {
continue;
}
let Some(export_decl) = result.arena.get_export_decl(stmt_node) else {
continue;
};
if !export_decl.is_default_export {
continue;
}
let Some(clause_node) = result.arena.get(export_decl.export_clause) else {
continue;
};
if clause_node.kind == crate::scanner::SyntaxKind::Identifier as u16 {
if let Some(ident) = result.arena.get_identifier(clause_node) {
default_export_old = result.file_locals.get(&ident.escaped_text);
}
} else if let Some(func) = result.arena.get_function(clause_node) {
if let Some(name_node) = result.arena.get(func.name)
&& let Some(ident) = result.arena.get_identifier(name_node)
{
default_export_old = result.file_locals.get(&ident.escaped_text);
}
} else if let Some(class) = result.arena.get_class(clause_node)
&& let Some(name_node) = result.arena.get(class.name)
&& let Some(ident) = result.arena.get_identifier(name_node)
{
default_export_old = result.file_locals.get(&ident.escaped_text);
}
break;
}
}
if let Some(old_sym_id) = default_export_old
&& let Some(&remapped_id) = id_remap.get(&old_sym_id)
{
exports.set("default".to_string(), remapped_id);
}
let remap_symbol_table =
|table: &SymbolTable, id_remap: &FxHashMap<SymbolId, SymbolId>| -> SymbolTable {
let mut remapped = SymbolTable::new();
for (name, old_sym_id) in table.iter() {
if let Some(&new_sym_id) = id_remap.get(old_sym_id) {
remapped.set(name.clone(), new_sym_id);
}
}
remapped
};
if !exports.is_empty() {
module_exports.insert(result.file_name.clone(), exports);
}
for (module_key, exports_table) in &result.module_exports {
if module_key == &result.file_name {
continue;
}
let remapped = remap_symbol_table(exports_table, &id_remap);
if !remapped.is_empty() {
module_exports.insert(module_key.clone(), remapped);
}
}
for (old_id, &new_id) in &id_remap {
if result.lib_symbol_ids.contains(old_id) {
continue;
}
let Some(old_sym) = result.symbols.get(*old_id) else {
continue;
};
for &decl_idx in &old_sym.declarations {
declaration_arenas
.entry((new_id, decl_idx))
.or_default()
.push(Arc::clone(&result.arena));
}
if let Some(new_sym) = global_symbols.get_mut(new_id) {
let is_cross_file_merge = !new_sym.declarations.is_empty();
if is_cross_file_merge {
new_sym.flags |= old_sym.flags;
append_unique_declarations(&mut new_sym.declarations, &old_sym.declarations);
if new_sym.value_declaration.is_none() && !old_sym.value_declaration.is_none() {
new_sym.value_declaration = old_sym.value_declaration;
}
if let (Some(old_exports), Some(new_exports)) =
(old_sym.exports.as_ref(), new_sym.exports.as_mut())
{
for (name, sym_id) in old_exports.iter() {
if !new_exports.has(name) {
if let Some(&remapped_id) = id_remap.get(sym_id) {
new_exports.set(name.clone(), remapped_id);
}
}
}
}
if let (Some(old_members), Some(new_members)) =
(old_sym.members.as_ref(), new_sym.members.as_mut())
{
for (name, sym_id) in old_members.iter() {
if !new_members.has(name) {
if let Some(&remapped_id) = id_remap.get(sym_id) {
new_members.set(name.clone(), remapped_id);
}
}
}
}
} else {
let mut updated = old_sym.clone();
updated.id = new_id;
updated.parent = id_remap
.get(&old_sym.parent)
.copied()
.unwrap_or(SymbolId::NONE);
updated.value_declaration = old_sym.value_declaration;
updated.declarations = old_sym.declarations.clone();
updated.is_exported = old_sym.is_exported;
updated.decl_file_idx = file_idx as u32;
updated.exports = old_sym
.exports
.as_ref()
.map(|table| Box::new(remap_symbol_table(table.as_ref(), &id_remap)));
updated.members = old_sym
.members
.as_ref()
.map(|table| Box::new(remap_symbol_table(table.as_ref(), &id_remap)));
*new_sym = updated;
}
}
}
let mut remapped_node_symbols = FxHashMap::default();
for (node_idx, old_sym_id) in &result.node_symbols {
if let Some(&new_sym_id) = id_remap.get(old_sym_id) {
remapped_node_symbols.insert(*node_idx, new_sym_id);
}
}
let mut remapped_file_locals = SymbolTable::new();
for (name, old_sym_id) in result.file_locals.iter() {
if let Some(&new_sym_id) = id_remap.get(old_sym_id) {
remapped_file_locals.set(name.clone(), new_sym_id);
let is_alias = global_symbols
.get(new_sym_id)
.is_some_and(|s| s.flags & crate::binder::symbol_flags::ALIAS != 0);
if !is_alias {
globals.set(name.clone(), new_sym_id);
}
} else {
let name_atom = name_interner.intern(name);
if let Some(&global_id) = lib_name_to_global.get(&name_atom) {
remapped_file_locals.set(name.clone(), global_id);
}
}
}
let mut remapped_scopes = Vec::with_capacity(result.scopes.len());
for scope in &result.scopes {
let mut table = SymbolTable::new();
for (name, old_sym_id) in scope.table.iter() {
if let Some(&new_sym_id) = id_remap.get(old_sym_id) {
table.set(name.clone(), new_sym_id);
}
}
remapped_scopes.push(Scope {
parent: scope.parent,
table,
kind: scope.kind,
container_node: scope.container_node,
});
}
file_locals_list.push(remapped_file_locals);
let module_augmentations: FxHashMap<String, Vec<crate::binder::ModuleAugmentation>> =
result
.module_augmentations
.iter()
.map(|(spec, augs)| {
let arena = Arc::clone(&result.arena);
(
spec.clone(),
augs.iter()
.map(|aug| {
crate::binder::ModuleAugmentation::with_arena(
aug.name.clone(),
aug.node,
Arc::clone(&arena),
)
})
.collect(),
)
})
.collect();
files.push(BoundFile {
file_name: result.file_name.clone(),
source_file: result.source_file,
arena: Arc::clone(&result.arena),
node_symbols: remapped_node_symbols,
scopes: remapped_scopes,
node_scope_ids: result.node_scope_ids.clone(),
parse_diagnostics: result.parse_diagnostics.clone(),
global_augmentations: result.global_augmentations.clone(),
module_augmentations,
flow_nodes: result.flow_nodes.clone(),
node_flow: result.node_flow.clone(),
switch_clause_to_switch: result.switch_clause_to_switch.clone(),
is_external_module: result.is_external_module,
expando_properties: result.expando_properties.clone(),
});
}
MergedProgram {
files,
symbols: global_symbols,
symbol_arenas,
declaration_arenas,
globals,
file_locals: file_locals_list,
declared_modules,
shorthand_ambient_modules,
module_exports,
reexports,
wildcard_reexports,
lib_binders,
lib_symbol_ids: global_lib_symbol_ids,
type_interner: TypeInterner::new(),
}
}
pub fn compile_files(files: Vec<(String, String)>) -> MergedProgram {
let lib_files = resolve_default_lib_files(ScriptTarget::ESNext)
.unwrap_or_else(|err| panic!("failed to resolve default lib files: {err}"));
compile_files_with_libs(files, &lib_files)
}
pub fn compile_files_with_libs(
files: Vec<(String, String)>,
lib_files: &[PathBuf],
) -> MergedProgram {
let lib_paths: Vec<&Path> = lib_files.iter().map(PathBuf::as_path).collect();
let bind_results = parse_and_bind_parallel_with_lib_files(files, &lib_paths);
merge_bind_results(bind_results)
}
use crate::checker::context::{CheckerOptions, LibContext};
use crate::checker::diagnostics::Diagnostic;
use crate::checker::state::CheckerState;
use crate::lib_loader::LibFile;
use crate::parser::syntax_kind_ext;
use tsz_solver::TypeId;
#[derive(Debug)]
pub struct FunctionCheckResult {
pub function_idx: NodeIndex,
pub file_idx: usize,
pub return_type: TypeId,
pub diagnostics: Vec<Diagnostic>,
}
pub struct FileCheckResult {
pub file_idx: usize,
pub file_name: String,
pub function_results: Vec<FunctionCheckResult>,
pub diagnostics: Vec<Diagnostic>,
}
pub struct CheckResult {
pub file_results: Vec<FileCheckResult>,
pub function_count: usize,
pub diagnostic_count: usize,
}
fn collect_functions(arena: &NodeArena, source_file: NodeIndex) -> Vec<NodeIndex> {
let mut functions = Vec::new();
let Some(sf) = arena.get_source_file_at(source_file) else {
return functions;
};
for &stmt_idx in &sf.statements.nodes {
collect_functions_from_node(arena, stmt_idx, &mut functions);
}
functions
}
fn collect_functions_from_node(
arena: &NodeArena,
node_idx: NodeIndex,
functions: &mut Vec<NodeIndex>,
) {
let Some(node) = arena.get(node_idx) else {
return;
};
match node.kind {
k if k == syntax_kind_ext::FUNCTION_DECLARATION
|| k == syntax_kind_ext::FUNCTION_EXPRESSION
|| k == syntax_kind_ext::ARROW_FUNCTION =>
{
functions.push(node_idx);
if let Some(func) = arena.get_function(node)
&& !func.body.is_none()
{
collect_functions_from_node(arena, func.body, functions);
}
}
k if k == syntax_kind_ext::METHOD_DECLARATION => {
functions.push(node_idx);
if let Some(method) = arena.get_method_decl(node)
&& !method.body.is_none()
{
collect_functions_from_node(arena, method.body, functions);
}
}
k if k == syntax_kind_ext::CLASS_DECLARATION => {
if let Some(class) = arena.get_class(node) {
for &member_idx in &class.members.nodes {
collect_functions_from_node(arena, member_idx, functions);
}
}
}
k if k == syntax_kind_ext::BLOCK => {
if let Some(block) = arena.get_block(node) {
for &stmt_idx in &block.statements.nodes {
collect_functions_from_node(arena, stmt_idx, &mut *functions);
}
}
}
k if k == syntax_kind_ext::VARIABLE_STATEMENT => {
if let Some(var_stmt) = arena.get_variable(node) {
for &decl_list_idx in &var_stmt.declarations.nodes {
if let Some(decl_list_node) = arena.get(decl_list_idx) {
if let Some(decl_list) = arena.get_variable(decl_list_node) {
for &decl_idx in &decl_list.declarations.nodes {
if let Some(decl_node) = arena.get(decl_idx)
&& let Some(decl) = arena.get_variable_declaration(decl_node)
&& !decl.initializer.is_none()
{
collect_functions_from_node(arena, decl.initializer, functions);
}
}
}
}
}
}
}
k if k == syntax_kind_ext::EXPORT_DECLARATION => {
if let Some(export) = arena.get_export_decl(node)
&& !export.export_clause.is_none()
{
collect_functions_from_node(arena, export.export_clause, functions);
}
}
_ => {}
}
}
pub fn check_functions_parallel(program: &MergedProgram) -> CheckResult {
let mut all_functions: Vec<(usize, NodeIndex)> = Vec::new();
for (file_idx, file) in program.files.iter().enumerate() {
let functions = collect_functions(&file.arena, file.source_file);
for func_idx in functions {
all_functions.push((file_idx, func_idx));
}
}
let function_count = all_functions.len();
let query_cache = tsz_solver::QueryCache::new(&program.type_interner);
let file_results: Vec<FileCheckResult> = maybe_parallel_iter!(program.files)
.enumerate()
.map(|(file_idx, file)| {
let functions = collect_functions(&file.arena, file.source_file);
let binder = create_binder_from_bound_file(file, program, file_idx);
let compiler_options = crate::checker::context::CheckerOptions::default();
let mut checker = CheckerState::new(
&file.arena,
&binder,
&query_cache,
file.file_name.clone(),
compiler_options, );
let mut function_results = Vec::new();
for func_idx in functions {
let return_type = checker.get_type_of_node(func_idx);
function_results.push(FunctionCheckResult {
function_idx: func_idx,
file_idx,
return_type,
diagnostics: Vec::new(), });
}
let diagnostics = std::mem::take(&mut checker.ctx.diagnostics);
FileCheckResult {
file_idx,
file_name: file.file_name.clone(),
function_results,
diagnostics,
}
})
.collect();
let diagnostic_count: usize = file_results.iter().map(|r| r.diagnostics.len()).sum();
CheckResult {
file_results,
function_count,
diagnostic_count,
}
}
pub fn check_files_parallel(
program: &MergedProgram,
checker_options: &CheckerOptions,
lib_files: &[Arc<LibFile>],
) -> CheckResult {
let lib_contexts: Vec<LibContext> = lib_files
.iter()
.map(|lib| LibContext {
arena: Arc::clone(&lib.arena),
binder: Arc::clone(&lib.binder),
})
.collect();
let query_cache = tsz_solver::QueryCache::new(&program.type_interner);
let file_results: Vec<FileCheckResult> = maybe_parallel_iter!(program.files)
.enumerate()
.map(|(file_idx, file)| {
let binder = create_binder_from_bound_file(file, program, file_idx);
let mut checker = CheckerState::with_options(
&file.arena,
&binder,
&query_cache,
file.file_name.clone(),
checker_options,
);
if !lib_contexts.is_empty() {
checker.ctx.set_lib_contexts(lib_contexts.clone());
checker.ctx.set_actual_lib_file_count(lib_contexts.len());
}
checker.check_source_file(file.source_file);
let diagnostics = std::mem::take(&mut checker.ctx.diagnostics);
FileCheckResult {
file_idx,
file_name: file.file_name.clone(),
function_results: Vec::new(),
diagnostics,
}
})
.collect();
let diagnostic_count: usize = file_results.iter().map(|r| r.diagnostics.len()).sum();
CheckResult {
file_results,
function_count: 0,
diagnostic_count,
}
}
pub fn create_binder_from_bound_file(
file: &BoundFile,
program: &MergedProgram,
file_idx: usize,
) -> BinderState {
let mut file_locals = SymbolTable::new();
if file_idx < program.file_locals.len() {
for (name, &sym_id) in program.file_locals[file_idx].iter() {
file_locals.set(name.clone(), sym_id);
}
}
for (name, &sym_id) in program.globals.iter() {
if !file_locals.has(name) {
file_locals.set(name.clone(), sym_id);
}
}
let mut merged_module_augmentations: rustc_hash::FxHashMap<
String,
Vec<crate::binder::ModuleAugmentation>,
> = rustc_hash::FxHashMap::default();
for other_file in &program.files {
for (spec, augs) in &other_file.module_augmentations {
merged_module_augmentations
.entry(spec.clone())
.or_default()
.extend(augs.clone());
}
}
let mut merged_global_augmentations: rustc_hash::FxHashMap<
String,
Vec<crate::binder::GlobalAugmentation>,
> = rustc_hash::FxHashMap::default();
for other_file in &program.files {
for (name, decls) in &other_file.global_augmentations {
merged_global_augmentations
.entry(name.clone())
.or_default()
.extend(decls.iter().map(|aug| {
crate::binder::GlobalAugmentation::with_arena(
aug.node,
Arc::clone(&other_file.arena),
)
}));
}
}
let mut binder = BinderState::from_bound_state_with_scopes_and_augmentations(
BinderOptions::default(),
program.symbols.clone(),
file_locals,
file.node_symbols.clone(),
BinderStateScopeInputs {
scopes: file.scopes.clone(),
node_scope_ids: file.node_scope_ids.clone(),
global_augmentations: merged_global_augmentations,
module_augmentations: merged_module_augmentations,
module_exports: program.module_exports.clone(),
reexports: program.reexports.clone(),
wildcard_reexports: program.wildcard_reexports.clone(),
symbol_arenas: program.symbol_arenas.clone(),
declaration_arenas: program.declaration_arenas.clone(),
shorthand_ambient_modules: program.shorthand_ambient_modules.clone(),
modules_with_export_equals: FxHashSet::default(),
flow_nodes: file.flow_nodes.clone(),
node_flow: file.node_flow.clone(),
switch_clause_to_switch: file.switch_clause_to_switch.clone(),
expando_properties: file.expando_properties.clone(),
},
);
binder.declared_modules = program.declared_modules.clone();
binder.set_lib_symbols_merged(true);
binder
}
pub fn check_functions_with_stats(program: &MergedProgram) -> (CheckResult, CheckStats) {
let result = check_functions_parallel(program);
let stats = CheckStats {
file_count: result.file_results.len(),
function_count: result.function_count,
diagnostic_count: result.diagnostic_count,
};
(result, stats)
}
#[derive(Debug, Clone)]
pub struct CheckStats {
pub file_count: usize,
pub function_count: usize,
pub diagnostic_count: usize,
}
pub fn parse_files_with_stats(files: Vec<(String, String)>) -> (Vec<ParseResult>, ParallelStats) {
let total_bytes: usize = files.iter().map(|(_, src)| src.len()).sum();
let file_count = files.len();
let results = parse_files_parallel(files);
let total_nodes: usize = results.iter().map(|r| r.arena.len()).sum();
let error_count: usize = results.iter().map(|r| r.parse_diagnostics.len()).sum();
let stats = ParallelStats {
file_count,
total_bytes,
total_nodes,
error_count,
};
(results, stats)
}
#[cfg(test)]
#[path = "../tests/parallel_tests.rs"]
mod tests;