use std::collections::{BTreeSet, HashMap, HashSet};
use oxc_ast::ast::{Declaration, IdentifierReference, Program, Statement, TSNamespaceDeclarationBody};
use oxc_ast_visit::Visit;
use oxc_semantic::{ScopeId, Scoping, SemanticBuilder};
use oxc_span::{GetSpan, Span as OxcSpan};
pub(crate) fn referenced_locals(
program: &Program<'_>,
wanted: &HashSet<String>,
excluded: &[OxcSpan],
) -> HashSet<String> {
let mut referenced = HashSet::new();
if wanted.is_empty() {
return referenced;
}
let semantic = SemanticBuilder::new().with_build_nodes(true).build(program).semantic;
let scoping = semantic.scoping();
let root = scoping.root_scope_id();
for symbol in scoping.symbol_ids() {
let name = scoping.symbol_name(symbol);
if scoping.symbol_scope_id(symbol) != root || !wanted.contains(name) {
continue;
}
let used = scoping.get_resolved_references(symbol).any(|reference| {
let span = semantic.reference_span(reference);
!excluded.iter().any(|e| e.start <= span.start && span.end <= e.end)
});
if used {
referenced.insert(name.to_string());
}
}
referenced
}
pub(crate) fn namespace_bare_reads(
program: &Program<'_>,
members: &HashMap<String, BTreeSet<String>>,
) -> HashMap<String, BTreeSet<String>> {
let semantic = SemanticBuilder::new().build(program).semantic;
let mut bodies = BareReads {
scoping: semantic.scoping(),
members,
current: None,
reads: HashMap::new(),
};
for stmt in &program.body {
let decl = match stmt {
Statement::ExportDeclaration(e) => Some(&e.declaration),
other => other.as_declaration(),
};
if let Some(Declaration::TSNamespaceDeclaration(ns)) = decl
&& members.contains_key(ns.id.name.as_str())
&& let TSNamespaceDeclarationBody::TSModuleBlock(block) = &ns.body
&& let Some(scope) = ns.scope_id.get()
{
let exported = block
.body
.iter()
.filter(|stmt| matches!(stmt, Statement::ExportDeclaration(_)))
.map(GetSpan::span)
.collect();
bodies.current = Some(Body {
name: ns.id.name.as_str(),
scope,
exported,
});
bodies.visit_ts_module_block(block);
bodies.current = None;
}
}
bodies.reads
}
struct Body<'n> {
name: &'n str,
scope: ScopeId,
exported: Vec<OxcSpan>,
}
struct BareReads<'s, 'm, 'n> {
scoping: &'s Scoping,
members: &'m HashMap<String, BTreeSet<String>>,
current: Option<Body<'n>>,
reads: HashMap<String, BTreeSet<String>>,
}
impl<'a> Visit<'a> for BareReads<'_, '_, '_> {
fn visit_identifier_reference(&mut self, it: &IdentifierReference<'a>) {
let Some(body) = &self.current else {
return;
};
let name = it.name.as_str();
let Some(members) = self.members.get(body.name) else {
return;
};
if !members.contains(name) {
return;
}
let symbol = it
.reference_id
.get()
.and_then(|reference| self.scoping.get_reference(reference).symbol_id());
let reads = match symbol {
Some(symbol) => {
let scope = self.scoping.symbol_scope_id(symbol);
if scope == body.scope {
let span = self.scoping.symbol_span(symbol);
body.exported.iter().any(|e| e.start <= span.start && span.end <= e.end)
} else {
!self.scoping.scope_is_descendant_of(scope, body.scope)
}
}
None => true,
};
if reads {
self.reads
.entry(body.name.to_owned())
.or_default()
.insert(name.to_owned());
}
}
}