use std::fmt;
use std::sync::Arc;
use lanekeep_core::FilePath;
use lanekeep_core::tracked::ContentHash;
use lanekeep_lang::binding::{Binding, BindingResolver, ImportedName};
use tree_sitter::{Node, Tree};
pub struct Declaration {
pub path: FilePath,
resolver: Arc<dyn BindingResolver>,
pub source: String,
pub tree: Tree,
pub hash: ContentHash,
pub has_error: bool,
}
impl fmt::Debug for Declaration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Declaration")
.field("path", &self.path)
.field("source_len", &self.source.len())
.field("hash", &self.hash)
.field("has_error", &self.has_error)
.finish_non_exhaustive()
}
}
impl Declaration {
#[must_use]
pub fn parse(
path: FilePath,
source: String,
parser: &mut tree_sitter::Parser,
resolver: Arc<dyn BindingResolver>,
) -> Option<Self> {
let hash = ContentHash::new(*blake3::hash(source.as_bytes()).as_bytes());
let tree = parser.parse(&source, None)?;
let has_error = tree.root_node().has_error();
Some(Self {
path,
resolver,
source,
tree,
hash,
has_error,
})
}
}
#[derive(Debug)]
pub enum Exported<'d> {
Here(Node<'d>),
From {
specifier: String,
name: String,
},
Namespace {
specifier: String,
},
Star(Vec<String>),
}
#[must_use]
pub fn find_export<'d>(decl: &'d Declaration, name: &str) -> Option<Exported<'d>> {
let root = decl.tree.root_node();
let mut cursor = root.walk();
let mut stars = Vec::new();
for statement in root.named_children(&mut cursor) {
if statement.kind() != "export_statement" {
continue;
}
let source = statement
.child_by_field_name("source")
.map(|node| unquote(text(decl, node)).to_owned());
if let Some(specifier) = source {
if let Some(clause) = named_child_of_kind(statement, "export_clause") {
if let Some(exported) = clause_target(decl, clause, name, &specifier) {
return Some(exported);
}
continue;
}
if let Some(namespace) = named_child_of_kind(statement, "namespace_export") {
if namespace
.named_child(0)
.is_some_and(|n| unquote(text(decl, n)) == name)
{
return Some(Exported::Namespace { specifier });
}
continue;
}
stars.push(specifier);
continue;
}
if let Some(clause) = named_child_of_kind(statement, "export_clause")
&& let Some(local) = local_clause_node(decl, clause, name)
{
if let Some(node) = declared_here(decl, unquote(text(decl, local))) {
return Some(Exported::Here(node));
}
if let Some(Binding::Import {
module,
name: imported,
}) = decl.resolver.resolve(&decl.tree, &decl.source, local)
{
return Some(match imported {
ImportedName::Named(exported) => Exported::From {
specifier: module,
name: exported,
},
ImportedName::Default => Exported::From {
specifier: module,
name: "default".to_owned(),
},
ImportedName::Namespace => Exported::Namespace { specifier: module },
});
}
}
let is_default = anonymous_child(statement, "default");
let is_export_assignment = anonymous_child(statement, "=");
if let Some(declaration) = statement.child_by_field_name("declaration") {
let wanted = if is_default { "default" } else { name };
if is_default && name == "default" {
return Some(Exported::Here(unwrap_ambient(declaration)));
}
if !is_default && let Some(node) = declares(decl, declaration, wanted) {
return Some(Exported::Here(node));
}
continue;
}
if (is_default || is_export_assignment) && name == "default" {
let value = statement
.child_by_field_name("value")
.or_else(|| statement.named_children(&mut statement.walk()).next())?;
if value.kind() == "identifier"
&& let Some(node) = declared_here(decl, text(decl, value))
{
return Some(Exported::Here(node));
}
return Some(Exported::Here(value));
}
}
(!stars.is_empty()).then_some(Exported::Star(stars))
}
#[must_use]
pub fn declared_here<'d>(decl: &'d Declaration, name: &str) -> Option<Node<'d>> {
declared_in(decl.resolver.as_ref(), &decl.tree, &decl.source, name)
}
#[must_use]
pub(crate) fn declared_in<'t>(
resolver: &dyn BindingResolver,
tree: &'t Tree,
source: &'t str,
name: &str,
) -> Option<Node<'t>> {
let root = tree.root_node();
let mut cursor = root.walk();
for statement in root.named_children(&mut cursor) {
let candidate = if statement.kind() == "export_statement" {
match statement.child_by_field_name("declaration") {
Some(declaration) => declaration,
None => continue,
}
} else {
statement
};
if let Some(found) = resolver.declares(source, candidate, name) {
return Some(found);
}
}
None
}
#[must_use]
pub fn declared_name(decl: &Declaration, node: Node<'_>) -> Option<String> {
let node = unwrap_ambient(node);
node.child_by_field_name("name")
.filter(|name| {
matches!(
name.kind(),
"identifier" | "type_identifier" | "nested_identifier" | "string"
)
})
.map(|name| unquote(text(decl, first_segment(name))).to_owned())
}
fn first_segment(name: Node<'_>) -> Node<'_> {
let mut node = name;
while matches!(node.kind(), "nested_identifier" | "member_expression") {
match node.child_by_field_name("object") {
Some(object) => node = object,
None => break,
}
}
node
}
fn declares<'d>(decl: &'d Declaration, declaration: Node<'d>, name: &str) -> Option<Node<'d>> {
decl.resolver.declares(&decl.source, declaration, name)
}
fn unwrap_ambient(node: Node<'_>) -> Node<'_> {
if node.kind() != "ambient_declaration" {
return node;
}
let mut cursor = node.walk();
node.named_children(&mut cursor)
.find(|child| child.kind() != "comment")
.unwrap_or(node)
}
fn clause_target<'d>(
decl: &'d Declaration,
clause: Node<'d>,
name: &str,
specifier: &str,
) -> Option<Exported<'d>> {
let mut cursor = clause.walk();
for specifier_node in clause.named_children(&mut cursor) {
if specifier_node.kind() != "export_specifier" {
continue;
}
let exported = specifier_node.child_by_field_name("name")?;
let visible = specifier_node
.child_by_field_name("alias")
.unwrap_or(exported);
if unquote(text(decl, visible)) == name {
return Some(Exported::From {
specifier: specifier.to_owned(),
name: unquote(text(decl, exported)).to_owned(),
});
}
}
None
}
fn local_clause_node<'d>(decl: &Declaration, clause: Node<'d>, name: &str) -> Option<Node<'d>> {
let mut cursor = clause.walk();
for specifier in clause.named_children(&mut cursor) {
if specifier.kind() != "export_specifier" {
continue;
}
let local = specifier.child_by_field_name("name")?;
let visible = specifier.child_by_field_name("alias").unwrap_or(local);
if unquote(text(decl, visible)) == name {
return Some(local);
}
}
None
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExportTarget {
pub file: FilePath,
pub name: String,
}
#[must_use]
pub(crate) fn target_node<'d>(decl: &'d Declaration, name: &str) -> Option<Node<'d>> {
declared_here(decl, name)
.or_else(|| match find_export(decl, name) {
Some(Exported::Here(node)) => Some(node),
_ => None,
})
.filter(|node| !node.has_error())
}
fn named_child_of_kind<'d>(node: Node<'d>, kind: &str) -> Option<Node<'d>> {
let mut cursor = node.walk();
node.named_children(&mut cursor).find(|c| c.kind() == kind)
}
fn anonymous_child(node: Node<'_>, token: &str) -> bool {
let mut cursor = node.walk();
node.children(&mut cursor)
.any(|child| !child.is_named() && child.kind() == token)
}
fn text<'d>(decl: &'d Declaration, node: Node<'_>) -> &'d str {
text_of(&decl.source, node)
}
fn text_of<'t>(source: &'t str, node: Node<'_>) -> &'t str {
source.get(node.byte_range()).unwrap_or("")
}
fn unquote(text: &str) -> &str {
let bytes = text.as_bytes();
match (bytes.first(), bytes.last()) {
(Some(b'"' | b'\''), Some(b'"' | b'\'')) if text.len() >= 2 => &text[1..text.len() - 1],
_ => text,
}
}
#[derive(Debug)]
pub(crate) struct ImportedSpecifier {
pub specifier: String,
pub names: Vec<ImportedName>,
}
#[must_use]
pub(crate) fn imports_with_names(tree: &Tree, source: &str) -> Vec<ImportedSpecifier> {
let root = tree.root_node();
let mut cursor = root.walk();
root.named_children(&mut cursor)
.filter(|statement| matches!(statement.kind(), "import_statement" | "export_statement"))
.filter_map(|statement| {
let specifier = statement.child_by_field_name("source").or_else(|| {
named_child_of_kind(statement, "import_require_clause")
.and_then(|clause| clause.child_by_field_name("source"))
})?;
Some(ImportedSpecifier {
specifier: unquote(text_of(source, specifier)).to_owned(),
names: bound_names(statement, source),
})
})
.collect()
}
fn bound_names(statement: Node<'_>, source: &str) -> Vec<ImportedName> {
match statement.kind() {
"import_statement" => {
let Some(clause) = named_child_of_kind(statement, "import_clause") else {
return Vec::new();
};
let mut names = Vec::new();
let mut cursor = clause.walk();
for child in clause.children(&mut cursor) {
match child.kind() {
"identifier" => names.push(ImportedName::Default),
"namespace_import" => names.push(ImportedName::Namespace),
"named_imports" => {
let mut inner = child.walk();
for specifier in child
.children(&mut inner)
.filter(|s| s.kind() == "import_specifier")
{
if let Some(exported) = specifier.child_by_field_name("name") {
names.push(ImportedName::Named(
unquote(text_of(source, exported)).to_owned(),
));
}
}
}
_ => {}
}
}
names
}
"export_statement" => {
if let Some(clause) = named_child_of_kind(statement, "export_clause") {
let mut names = Vec::new();
let mut cursor = clause.walk();
for specifier in clause
.named_children(&mut cursor)
.filter(|s| s.kind() == "export_specifier")
{
if let Some(exported) = specifier.child_by_field_name("name") {
names.push(ImportedName::Named(
unquote(text_of(source, exported)).to_owned(),
));
}
}
return names;
}
if named_child_of_kind(statement, "namespace_export").is_some() {
return vec![ImportedName::Namespace];
}
Vec::new()
}
_ => Vec::new(),
}
}