use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use crate::stack::imports::{
module_binding, script_module, symbol_binding, BindingReach, ImportSite,
};
use crate::syntax::{SyntaxGraph, SyntaxNode};
use crate::{CodeGraph, NodeId};
pub fn require_sites(
syntax: &SyntaxGraph,
bound_names: &BTreeMap<NodeId, (NodeId, &str)>,
) -> Vec<ImportSite> {
bound_names
.iter()
.flat_map(|(node, (scope, symbol))| declaration_sites(syntax, *node, *scope, symbol))
.collect()
}
fn declaration_sites(
syntax: &SyntaxGraph,
node: NodeId,
scope: NodeId,
symbol: &str,
) -> Vec<ImportSite> {
let Some(SyntaxNode::VariableDeclaration {
value_id: Some(value),
variable_id,
..
}) = syntax.nodes.get(&node)
else {
return Vec::new();
};
let Some((module, relative_level)) = required_module(syntax, *value) else {
return Vec::new();
};
let site = |bound, name: &str, binding| ImportSite {
node: bound,
scope,
symbol: String::from(name),
binding,
reach: BindingReach::Scope,
};
match variable_id.and_then(|pattern| syntax.nodes.get(&pattern).map(|kind| (pattern, kind))) {
Some((pattern, SyntaxNode::Object { .. })) => syntax
.ast_children(pattern)
.into_iter()
.filter_map(|member| {
let (bound, local, imported) = pattern_member(syntax, member)?;
Some(site(
bound,
local,
symbol_binding(&module, relative_level, imported, local),
))
})
.collect(),
None if syntax.ast_children(node).contains(value) => {
Vec::from([site(node, symbol, module_binding(&module, relative_level))])
}
_ => Vec::new(),
}
}
fn pattern_member(syntax: &SyntaxGraph, member: NodeId) -> Option<(NodeId, &str, &str)> {
match syntax.nodes.get(&member)? {
SyntaxNode::VariableDeclaration { variable, .. } => Some((member, variable, variable)),
SyntaxNode::Pair { key_id, value_id } => {
let imported = looked_up_symbol(syntax, *key_id)?;
let local = looked_up_symbol(syntax, *value_id)?;
Some((*value_id, local, imported))
}
_ => None,
}
}
fn looked_up_symbol(syntax: &SyntaxGraph, node: NodeId) -> Option<&str> {
match syntax.nodes.get(&node)? {
SyntaxNode::SymbolLookup { symbol, .. } => Some(symbol),
_ => None,
}
}
fn required_module(syntax: &SyntaxGraph, value: NodeId) -> Option<(String, u32)> {
let SyntaxNode::MethodInvocation {
expression,
object: None,
arguments_id: Some(arguments),
..
} = syntax.nodes.get(&value)?
else {
return None;
};
if expression != "require" {
return None;
}
match syntax.nodes.get(syntax.ast_children(*arguments).first()?)? {
SyntaxNode::Literal {
value: specifier,
value_type,
} if value_type == "string" => Some(script_module(specifier)),
_ => None,
}
}