use std::cell::Cell;
use std::fmt;
use std::sync::Arc;
use lanekeep_core::FilePath;
use lanekeep_lang::Language;
use lanekeep_lang::binding::{Binding, BindingResolver, ImportedName};
use tree_sitter::{Node, Tree};
use crate::declarations::ExportTarget;
use crate::table;
use crate::types::{Primitive, Symbol, Type};
pub trait ImportResolution {
fn imported_value_type(
&self,
from: &FilePath,
module: &str,
name: &ImportedName,
depth: u32,
) -> Option<Type>;
fn imported_alias_type(
&self,
from: &FilePath,
module: &str,
name: &ImportedName,
depth: u32,
) -> Followed;
fn imported_return_type(
&self,
from: &FilePath,
module: &str,
name: &ImportedName,
depth: u32,
) -> Option<Type>;
fn imported_export(
&self,
from: &FilePath,
module: &str,
name: &ImportedName,
) -> Option<ExportTarget>;
}
const REQUIRED_KINDS: &[&str] = &[
"predefined_type",
"type_annotation",
"type_identifier",
"union_type",
"literal_type",
"type_alias_declaration",
"type_parameter",
"identifier",
"required_parameter",
"optional_parameter",
"variable_declarator",
"comment",
"string",
"template_string",
"true",
"false",
"null",
"undefined",
"number",
"parenthesized_expression",
"binary_expression",
"unary_expression",
"call_expression",
"export_statement",
"export_clause",
"export_specifier",
"namespace_export",
"ambient_declaration",
"lexical_declaration",
"variable_declaration",
"function_signature",
"function_declaration",
"generator_function_declaration",
"class_declaration",
"abstract_class_declaration",
"interface_declaration",
"enum_declaration",
"module",
"internal_module",
"class_heritage",
"extends_clause",
"extends_type_clause",
"import_statement",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Followed {
Type(Type),
Exhausted,
NotAnAlias,
}
pub(crate) const MAX_DEPTH: u32 = 16;
pub struct TypeScriptOracle<'t> {
tree: &'t Tree,
source: &'t str,
resolver: Arc<dyn BindingResolver>,
file: Option<&'t FilePath>,
imports: Option<&'t dyn ImportResolution>,
exhausted: Option<&'t Cell<bool>>,
}
impl fmt::Debug for TypeScriptOracle<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypeScriptOracle")
.field("tree", &self.tree)
.field("source_len", &self.source.len())
.field("has_imports", &self.imports.is_some())
.finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct TypeScriptSupport {
resolver: Arc<dyn BindingResolver>,
}
impl fmt::Debug for TypeScriptSupport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypeScriptSupport").finish_non_exhaustive()
}
}
impl TypeScriptSupport {
#[must_use]
pub fn probe(language: &dyn Language) -> Option<Self> {
let grammar = language.grammar();
if !REQUIRED_KINDS
.iter()
.all(|kind| grammar.id_for_node_kind(kind, true) != 0)
{
return None;
}
Some(Self {
resolver: language.resolver()?,
})
}
pub(crate) fn resolver(&self) -> &Arc<dyn BindingResolver> {
&self.resolver
}
}
impl<'t> TypeScriptOracle<'t> {
#[must_use]
pub fn new(support: &TypeScriptSupport, tree: &'t Tree, source: &'t str) -> Self {
Self {
tree,
source,
resolver: Arc::clone(&support.resolver),
file: None,
imports: None,
exhausted: None,
}
}
#[must_use]
pub fn with_imports(mut self, file: &'t FilePath, imports: &'t dyn ImportResolution) -> Self {
self.file = Some(file);
self.imports = Some(imports);
self
}
#[must_use]
pub fn with_exhaustion(mut self, exhausted: &'t Cell<bool>) -> Self {
self.exhausted = Some(exhausted);
self
}
fn exhaust<T>(&self) -> Option<T> {
if let Some(flag) = self.exhausted {
flag.set(true);
}
None
}
#[must_use]
pub fn type_of_from(&self, node: Node<'t>, depth: u32) -> Option<Type> {
self.type_of_at(node, depth)
}
#[must_use]
pub fn declaration_type_from(&self, declaration: Node<'t>, depth: u32) -> Option<Type> {
self.declaration_type(declaration, depth)
}
#[must_use]
pub fn return_type_from(&self, node: Node<'t>, depth: u32) -> Option<Type> {
self.return_type_at(node, depth)
}
#[must_use]
pub fn type_named_by(&self, node: Node<'t>) -> Option<Type> {
self.named_type(node, 0)
}
#[must_use]
pub fn type_of(&self, node: Node<'t>) -> Option<Type> {
self.type_of_at(node, 0)
}
#[must_use]
pub fn symbol_of(&self, node: Node<'t>) -> Option<Symbol> {
self.symbol_at(node)
}
#[must_use]
pub fn return_type_of(&self, node: Node<'t>) -> Option<Type> {
self.return_type_at(node, 0)
}
fn return_type_at(&self, node: Node<'t>, depth: u32) -> Option<Type> {
if depth >= MAX_DEPTH {
return self.exhaust();
}
let next = depth.saturating_add(1);
match node.kind() {
"call_expression" => self.return_type_at(node.child_by_field_name("function")?, next),
"identifier" => {
if let Some(Binding::Import { module, name }) =
self.resolver.resolve(self.tree, self.source, node)
&& let (Some(file), Some(imports)) = (self.file, self.imports)
{
return imports.imported_return_type(file, &module, &name, next);
}
let declaration = self.resolver.declaration_of(self.tree, self.source, node)?;
self.return_type_at(declaration, next)
}
"variable_declarator" => self.return_type_at(node.child_by_field_name("value")?, next),
"function_declaration"
| "generator_function_declaration"
| "function_signature"
| "function_expression"
| "generator_function"
| "arrow_function"
| "method_definition"
| "method_signature"
| "abstract_method_signature" => self.signature_return(node, next),
_ => None,
}
}
fn signature_return(&self, node: Node<'t>, depth: u32) -> Option<Type> {
if let Some(annotation) = node.child_by_field_name("return_type") {
return self.annotation_type(annotation_child(annotation)?, depth);
}
if wraps_its_return(node) {
return None;
}
let body = node.child_by_field_name("body")?;
if body.kind() != "statement_block" {
return self.type_of_at(body, depth);
}
let mut returns = Vec::new();
collect_returns(body, &mut returns);
if returns.is_empty() {
return None;
}
let members: Vec<Type> = returns
.into_iter()
.map(|returned| match returned {
None => Some(Type::Primitive(Primitive::Undefined)),
Some(expression) => self.type_of_at(expression, depth),
})
.collect::<Option<Vec<Type>>>()?;
Type::union(members)
}
fn type_of_at(&self, node: Node<'t>, depth: u32) -> Option<Type> {
if depth >= MAX_DEPTH {
return self.exhaust();
}
match node.kind() {
"string" | "template_string" => Some(Type::Primitive(Primitive::String)),
"true" | "false" => Some(Type::Primitive(Primitive::Boolean)),
"null" => Some(Type::Primitive(Primitive::Null)),
"undefined" => Some(Type::Primitive(Primitive::Undefined)),
"number" => Some(Type::Primitive(if self.text(node).ends_with('n') {
Primitive::BigInt
} else {
Primitive::Number
})),
"parenthesized_expression" => {
self.type_of_at(node.named_child(0)?, depth.saturating_add(1))
}
"binary_expression" => {
let next = depth.saturating_add(1);
let left = self.primitive_of(node.child_by_field_name("left")?, next);
let right = self.primitive_of(node.child_by_field_name("right")?, next);
table::binary(self.operator_of(node)?, left, right).map(Type::Primitive)
}
"unary_expression" => table::unary(self.operator_of(node)?).map(Type::Primitive),
"call_expression" => {
let callee = node.child_by_field_name("function")?;
if callee.kind() != "identifier" {
return None;
}
if self
.resolver
.resolve(self.tree, self.source, callee)
.is_some()
{
return None;
}
table::builtin_call(self.text(callee)).map(Type::Primitive)
}
"type_annotation" => {
self.annotation_type(node.named_child(0)?, depth.saturating_add(1))
}
"predefined_type" | "union_type" | "literal_type" | "type_identifier" => {
self.annotation_type(node, depth)
}
"identifier" => {
if let Some(Binding::Import { module, name }) =
self.resolver.resolve(self.tree, self.source, node)
&& let (Some(file), Some(imports)) = (self.file, self.imports)
{
return imports.imported_value_type(
file,
&module,
&name,
depth.saturating_add(1),
);
}
let declaration = self.resolver.declaration_of(self.tree, self.source, node)?;
self.declaration_type(declaration, depth.saturating_add(1))
}
_ => None,
}
}
fn declaration_type(&self, declaration: Node<'t>, depth: u32) -> Option<Type> {
if depth >= MAX_DEPTH {
return self.exhaust();
}
let next = depth.saturating_add(1);
match declaration.kind() {
"required_parameter" | "optional_parameter" => {
if !binds_one_name(declaration, "pattern") {
return None;
}
let annotation = declaration.child_by_field_name("type")?;
self.annotation_type(annotation_child(annotation)?, next)
}
"variable_declarator" => {
if !binds_one_name(declaration, "name") {
return None;
}
if let Some(annotation) = declaration.child_by_field_name("type") {
return self.annotation_type(annotation_child(annotation)?, next);
}
self.type_of_at(declaration.child_by_field_name("value")?, next)
}
_ => None,
}
}
fn primitive_of(&self, node: Node<'t>, depth: u32) -> Option<Primitive> {
match self.type_of_at(node, depth)? {
Type::Primitive(primitive) => Some(primitive),
Type::Nominal { .. } | Type::Union(_) => None,
}
}
fn annotation_type(&self, node: Node<'t>, depth: u32) -> Option<Type> {
if depth >= MAX_DEPTH {
return self.exhaust();
}
match node.kind() {
"predefined_type" => match self.text(node) {
"number" => Some(Type::Primitive(Primitive::Number)),
"string" => Some(Type::Primitive(Primitive::String)),
"boolean" => Some(Type::Primitive(Primitive::Boolean)),
"symbol" => Some(Type::Primitive(Primitive::Symbol)),
_ => None,
},
"union_type" => {
let next = depth.saturating_add(1);
let mut cursor = node.walk();
let members: Vec<Type> = node
.children(&mut cursor)
.filter(|child| child.is_named() && child.kind() != "comment")
.map(|member| self.annotation_type(member, next))
.collect::<Option<Vec<Type>>>()?;
Type::union(members)
}
"literal_type" => self.type_of_at(node.named_child(0)?, depth.saturating_add(1)),
"type_identifier" => {
if self.text(node) == "bigint"
&& self
.resolver
.resolve(self.tree, self.source, node)
.is_none()
{
return Some(Type::Primitive(Primitive::BigInt));
}
self.named_type(node, depth)
}
_ => None,
}
}
fn named_type(&self, node: Node<'t>, depth: u32) -> Option<Type> {
let name = self.text(node);
if name.is_empty() {
return None;
}
if let Some(declaration) = self.resolver.declaration_of(self.tree, self.source, node) {
if declaration.kind() == "type_parameter" {
return None;
}
if declaration.kind() == "type_alias_declaration"
&& let Some(value) = declaration.child_by_field_name("value")
{
return self.annotation_type(value, depth.saturating_add(1));
}
}
if let Some(Binding::Import {
module,
name: imported,
}) = self.resolver.resolve(self.tree, self.source, node)
&& let (Some(file), Some(imports)) = (self.file, self.imports)
{
match imports.imported_alias_type(file, &module, &imported, depth.saturating_add(1)) {
Followed::Type(aliased) => return Some(aliased),
Followed::Exhausted => return self.exhaust(),
Followed::NotAnAlias => {}
}
}
Some(Type::Nominal {
name: name.to_owned(),
symbol: self.symbol_at(node),
})
}
fn symbol_at(&self, node: Node<'t>) -> Option<Symbol> {
let name = self.text(node);
if name.is_empty() {
return None;
}
let (module, exported) = match self.resolver.resolve(self.tree, self.source, node)? {
Binding::Import {
module,
name: imported,
} => {
let declared = self
.file
.zip(self.imports)
.and_then(|(file, imports)| imports.imported_export(file, &module, &imported))
.map(|target| target.name);
let exported = declared.or(match &imported {
ImportedName::Named(exported) => Some(exported.clone()),
ImportedName::Default => Some("default".to_owned()),
ImportedName::Namespace => None,
});
(Some(module), exported)
}
Binding::Local(_) => (None, None),
};
Some(Symbol {
name: name.to_owned(),
module,
exported,
})
}
fn operator_of(&self, node: Node<'t>) -> Option<&'t str> {
node.child_by_field_name("operator")
.map(|child| self.text(child))
}
fn text(&self, node: Node<'t>) -> &'t str {
self.source.get(node.byte_range()).unwrap_or("")
}
}
fn binds_one_name(declaration: Node<'_>, field: &str) -> bool {
declaration
.child_by_field_name(field)
.is_some_and(|bound| bound.kind() == "identifier")
}
fn wraps_its_return(node: Node<'_>) -> bool {
let mut cursor = node.walk();
node.children(&mut cursor)
.any(|child| !child.is_named() && matches!(child.kind(), "async" | "*"))
}
fn collect_returns<'t>(node: Node<'t>, out: &mut Vec<Option<Node<'t>>>) {
let mut stack = vec![node];
while let Some(current) = stack.pop() {
if current.kind() == "return_statement" {
out.push(current.named_child(0));
continue;
}
if current.id() != node.id() && is_function_like(current) {
continue;
}
let mut cursor = current.walk();
let children: Vec<Node<'t>> = current.children(&mut cursor).collect();
stack.extend(children.into_iter().rev());
}
}
fn is_function_like(node: Node<'_>) -> bool {
matches!(
node.kind(),
"function_declaration"
| "generator_function_declaration"
| "function_signature"
| "function_expression"
| "generator_function"
| "arrow_function"
| "method_definition"
| "method_signature"
| "abstract_method_signature"
)
}
fn annotation_child(node: Node<'_>) -> Option<Node<'_>> {
if node.kind() == "type_annotation" {
node.named_child(0)
} else {
Some(node)
}
}