use crate::error::{Error, Result};
use crate::parser::doc_comment::{parse_jsdoc, parse_type_string, ParsedDocComment};
use crate::parser::Parser;
use crate::schema::*;
use std::path::Path;
use tree_sitter::{Node, Parser as TsParser, Tree};
pub struct TypeScriptParser {
ts_parser: TsParser,
source: String,
file_path: String,
context_stack: Vec<String>,
}
impl TypeScriptParser {
pub fn new() -> Result<Self> {
let mut ts_parser = TsParser::new();
ts_parser
.set_language(&tree_sitter_typescript::language_typescript())
.map_err(|e| Error::tree_sitter(format!("Failed to load TypeScript grammar: {}", e)))?;
Ok(Self {
ts_parser,
source: String::new(),
file_path: String::new(),
context_stack: Vec::new(),
})
}
pub fn parse(&mut self, source: &str) -> Result<Tree> {
self.source = source.to_string();
self.ts_parser
.parse(source, None)
.ok_or_else(|| Error::tree_sitter("Failed to parse TypeScript source"))
}
fn current_prefix(&self) -> String {
self.context_stack.join(".")
}
fn make_id(&self, name: &str, separator: &str) -> String {
let prefix = self.current_prefix();
if prefix.is_empty() {
name.to_string()
} else {
format!("{}{}{}", prefix, separator, name)
}
}
fn node_text(&self, node: Node) -> &str {
node.utf8_text(self.source.as_bytes()).unwrap_or("")
}
fn node_location(&self, node: Node) -> SourceLocation {
let point = node.start_position();
SourceLocation {
file: self.file_path.clone(),
line: point.row as u32 + 1,
column: Some(point.column as u32),
}
}
fn find_preceding_comment(&self, node: Node) -> Option<String> {
let mut current = node;
while let Some(prev) = current.prev_sibling() {
match prev.kind() {
"comment" => {
let text = self.node_text(prev);
if text.starts_with("/**") {
return Some(text.to_string());
}
current = prev;
}
_ if !prev.is_named() => {
current = prev;
}
_ => break,
}
}
None
}
fn parse_class(&mut self, node: Node) -> Option<Class> {
let name_node = node.child_by_field_name("name")?;
let name = self.node_text(name_node).to_string();
let id = self.make_id(&name, ".");
let doc_comment = self.find_preceding_comment(node);
let parsed_doc = doc_comment.as_ref().map(|c| parse_jsdoc(c));
let type_params = self.parse_type_parameters(node);
let extends = node
.children_by_field_name("heritage", &mut node.walk())
.find_map(|h| {
if self.node_text(h).starts_with("extends") {
h.named_child(0).map(|n| {
let name = self.node_text(n).to_string();
TypeReference::simple(name)
})
} else {
None
}
});
let implements: Vec<TypeReference> = node
.children_by_field_name("heritage", &mut node.walk())
.filter_map(|h| {
if self.node_text(h).starts_with("implements") {
Some(
h.named_children(&mut h.walk())
.map(|n| TypeReference::simple(self.node_text(n).to_string()))
.collect::<Vec<_>>(),
)
} else {
None
}
})
.flatten()
.collect();
let is_abstract = {
let mut cursor = node.walk();
let children: Vec<_> = node.children(&mut cursor).collect();
children
.iter()
.any(|c| c.kind() == "abstract" || self.node_text(*c) == "abstract")
};
let mut class = Class {
base: BaseEntity {
id: id.clone(),
name: name.clone(),
location: Some(self.node_location(node)),
docs: parsed_doc.as_ref().map(|d| d.to_documentation()),
visibility: Some(Visibility::Public),
is_private_api: parsed_doc.as_ref().and_then(|d| {
if d.is_private {
Some(true)
} else {
None
}
}),
},
extends,
implements,
includes: Vec::new(),
prepends: Vec::new(),
type_params,
is_abstract: if is_abstract { Some(true) } else { None },
constructor: None,
properties: Vec::new(),
methods: Vec::new(),
static_properties: Vec::new(),
static_methods: Vec::new(),
constants: Vec::new(),
nested: Vec::new(),
};
self.context_stack.push(name.clone());
if let Some(body) = node.child_by_field_name("body") {
self.parse_class_body(&mut class, body);
}
self.context_stack.pop();
Some(class)
}
fn parse_class_body(&mut self, class: &mut Class, body: Node) {
use std::collections::HashMap;
let mut overloads: HashMap<String, Vec<Callable>> = HashMap::new();
let mut cursor = body.walk();
for child in body.named_children(&mut cursor) {
if child.kind() == "method_signature" {
if let Some(sig) = self.parse_method_signature(child) {
overloads
.entry(sig.base.name.clone())
.or_default()
.push(sig);
}
}
}
let mut accessors: HashMap<String, (Option<Property>, Option<Property>, bool)> =
HashMap::new();
let mut cursor = body.walk();
for child in body.named_children(&mut cursor) {
match child.kind() {
"method_definition" => {
let text = self.node_text(child).to_string();
let is_getter = text.trim_start().starts_with("get ");
let is_setter = text.trim_start().starts_with("set ");
let is_static = text.contains("static ");
if is_getter || is_setter {
if let Some(prop) = self.parse_accessor(child, is_getter) {
let entry = accessors
.entry(prop.base.name.clone())
.or_insert((None, None, is_static));
if is_getter {
entry.0 = Some(prop);
} else {
entry.1 = Some(prop);
}
}
} else if let Some(mut method) = self.parse_method(child) {
if let Some(method_overloads) = overloads.remove(&method.base.name) {
method.overloads = method_overloads;
}
let is_constructor = method.base.name == "constructor";
if is_constructor {
class.constructor = Some(Box::new(method));
} else if is_static {
class.static_methods.push(method);
} else {
class.methods.push(method);
}
}
}
"public_field_definition" | "property_signature" => {
if let Some(prop) = self.parse_property(child) {
let is_static = self.node_text(child).contains("static ");
if is_static {
class.static_properties.push(prop);
} else {
class.properties.push(prop);
}
}
}
_ => {}
}
}
for (_name, (getter, setter, is_static)) in accessors {
let prop = match (getter, setter) {
(Some(mut g), Some(s)) => {
g.getter = Some(true);
g.setter = Some(true);
if g.base.docs.is_none() && s.base.docs.is_some() {
g.base.docs = s.base.docs;
}
g
}
(Some(mut g), None) => {
g.getter = Some(true);
g.readonly = Some(true); g
}
(None, Some(mut s)) => {
s.setter = Some(true);
s
}
(None, None) => continue,
};
if is_static {
class.static_properties.push(prop);
} else {
class.properties.push(prop);
}
}
}
fn parse_interface(&mut self, node: Node) -> Option<Interface> {
let name_node = node.child_by_field_name("name")?;
let name = self.node_text(name_node).to_string();
let id = self.make_id(&name, ".");
let doc_comment = self.find_preceding_comment(node);
let parsed_doc = doc_comment.as_ref().map(|c| parse_jsdoc(c));
let type_params = self.parse_type_parameters(node);
let mut extends: Vec<TypeReference> = Vec::new();
for h in node.children_by_field_name("heritage", &mut node.walk()) {
let mut cursor = h.walk();
for n in h.named_children(&mut cursor) {
extends.push(TypeReference::simple(self.node_text(n).to_string()));
}
}
let mut interface = Interface {
base: BaseEntity {
id: id.clone(),
name: name.clone(),
location: Some(self.node_location(node)),
docs: parsed_doc.as_ref().map(|d| d.to_documentation()),
visibility: Some(Visibility::Public),
is_private_api: parsed_doc.as_ref().and_then(|d| {
if d.is_private {
Some(true)
} else {
None
}
}),
},
extends,
type_params,
properties: Vec::new(),
methods: Vec::new(),
call_signatures: Vec::new(),
index_signatures: Vec::new(),
};
self.context_stack.push(name.clone());
if let Some(body) = node.child_by_field_name("body") {
self.parse_interface_body(&mut interface, body);
}
self.context_stack.pop();
Some(interface)
}
fn parse_interface_body(&mut self, interface: &mut Interface, body: Node) {
let mut cursor = body.walk();
for child in body.named_children(&mut cursor) {
match child.kind() {
"property_signature" => {
if let Some(prop) = self.parse_property(child) {
interface.properties.push(prop);
}
}
"method_signature" => {
if let Some(method) = self.parse_method_signature(child) {
interface.methods.push(method);
}
}
"call_signature" => {
if let Some(sig) = self.parse_call_signature(child) {
interface.call_signatures.push(sig);
}
}
"index_signature" => {
if let Some(sig) = self.parse_index_signature(child) {
interface.index_signatures.push(sig);
}
}
_ => {}
}
}
}
fn parse_type_alias(&mut self, node: Node) -> Option<TypeAlias> {
let name_node = node.child_by_field_name("name")?;
let name = self.node_text(name_node).to_string();
let id = self.make_id(&name, ".");
let doc_comment = self.find_preceding_comment(node);
let parsed_doc = doc_comment.as_ref().map(|c| parse_jsdoc(c));
let type_params = self.parse_type_parameters(node);
let aliased_type = node
.child_by_field_name("value")
.map(|n| self.parse_type_node(n));
Some(TypeAlias {
base: BaseEntity {
id,
name,
location: Some(self.node_location(node)),
docs: parsed_doc.as_ref().map(|d| d.to_documentation()),
visibility: Some(Visibility::Public),
is_private_api: parsed_doc.as_ref().and_then(|d| {
if d.is_private {
Some(true)
} else {
None
}
}),
},
aliased_type,
type_params,
})
}
fn parse_enum(&mut self, node: Node) -> Option<Enum> {
let name_node = node.child_by_field_name("name")?;
let name = self.node_text(name_node).to_string();
let id = self.make_id(&name, ".");
let doc_comment = self.find_preceding_comment(node);
let parsed_doc = doc_comment.as_ref().map(|c| parse_jsdoc(c));
let mut members = Vec::new();
if let Some(body) = node.child_by_field_name("body") {
let mut cursor = body.walk();
for child in body.named_children(&mut cursor) {
if child.kind() == "enum_assignment" || child.kind() == "property_identifier" {
let member_name = if child.kind() == "enum_assignment" {
child
.child_by_field_name("name")
.map(|n| self.node_text(n).to_string())
} else {
Some(self.node_text(child).to_string())
};
let value = child
.child_by_field_name("value")
.map(|n| serde_json::Value::String(self.node_text(n).to_string()));
if let Some(name) = member_name {
members.push(EnumMember {
name,
value,
docs: None,
});
}
}
}
}
Some(Enum {
base: BaseEntity {
id,
name,
location: Some(self.node_location(node)),
docs: parsed_doc.as_ref().map(|d| d.to_documentation()),
visibility: Some(Visibility::Public),
is_private_api: parsed_doc.as_ref().and_then(|d| {
if d.is_private {
Some(true)
} else {
None
}
}),
},
members,
})
}
fn parse_method(&mut self, node: Node) -> Option<Callable> {
let name_node = node.child_by_field_name("name")?;
let name = self.node_text(name_node).to_string();
let is_static = self.node_text(node).contains("static ");
let separator = if is_static || name == "constructor" {
"."
} else {
"#"
};
let id = self.make_id(&name, separator);
let doc_comment = self.find_preceding_comment(node);
let parsed_doc = doc_comment.as_ref().map(|c| parse_jsdoc(c));
let type_params = self.parse_type_parameters(node);
let params = self.parse_parameters(node, parsed_doc.as_ref());
let return_type = self.parse_return_type(node, parsed_doc.as_ref());
let is_async = self.node_text(node).contains("async ");
let is_abstract = self.node_text(node).contains("abstract ");
Some(Callable {
base: BaseEntity {
id,
name,
location: Some(self.node_location(node)),
docs: parsed_doc.as_ref().map(|d| d.to_documentation()),
visibility: self.parse_visibility(node),
is_private_api: parsed_doc.as_ref().and_then(|d| {
if d.is_private {
Some(true)
} else {
None
}
}),
},
params,
returns: return_type,
returns_description: parsed_doc.as_ref().and_then(|d| d.return_description()),
throws: parsed_doc
.as_ref()
.map(|d| d.to_throws())
.unwrap_or_default(),
yields: None,
is_async: if is_async { Some(true) } else { None },
is_static: if is_static { Some(true) } else { None },
is_abstract: if is_abstract { Some(true) } else { None },
generator: None,
type_params,
overloads: Vec::new(),
})
}
fn parse_method_signature(&mut self, node: Node) -> Option<Callable> {
let name_node = node.child_by_field_name("name")?;
let name = self.node_text(name_node).to_string();
let id = self.make_id(&name, "#");
let doc_comment = self.find_preceding_comment(node);
let parsed_doc = doc_comment.as_ref().map(|c| parse_jsdoc(c));
let type_params = self.parse_type_parameters(node);
let params = self.parse_parameters(node, parsed_doc.as_ref());
let return_type = self.parse_return_type(node, parsed_doc.as_ref());
Some(Callable {
base: BaseEntity {
id,
name,
location: Some(self.node_location(node)),
docs: parsed_doc.as_ref().map(|d| d.to_documentation()),
visibility: Some(Visibility::Public),
is_private_api: None,
},
params,
returns: return_type,
returns_description: parsed_doc.as_ref().and_then(|d| d.return_description()),
throws: Vec::new(),
yields: None,
is_async: None,
is_static: None,
is_abstract: None,
generator: None,
type_params,
overloads: Vec::new(),
})
}
fn parse_call_signature(&mut self, node: Node) -> Option<Callable> {
let id = self.make_id("__call", ".");
let type_params = self.parse_type_parameters(node);
let params = self.parse_parameters(node, None);
let return_type = self.parse_return_type(node, None);
Some(Callable {
base: BaseEntity {
id,
name: "__call".to_string(),
location: Some(self.node_location(node)),
docs: None,
visibility: Some(Visibility::Public),
is_private_api: None,
},
params,
returns: return_type,
returns_description: None,
throws: Vec::new(),
yields: None,
is_async: None,
is_static: None,
is_abstract: None,
generator: None,
type_params,
overloads: Vec::new(),
})
}
fn parse_index_signature(&self, node: Node) -> Option<IndexSignature> {
let mut key_type = None;
let mut value_type = None;
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if child.kind() == "index_type_query" || child.kind().contains("type") {
if key_type.is_none() {
key_type = Some(self.parse_type_node(child));
} else {
value_type = Some(self.parse_type_node(child));
}
}
}
Some(IndexSignature {
key_type,
value_type,
readonly: if self.node_text(node).contains("readonly") {
Some(true)
} else {
None
},
})
}
fn parse_property(&mut self, node: Node) -> Option<Property> {
let name_node = node.child_by_field_name("name")?;
let name = self.node_text(name_node).to_string();
let is_static = self.node_text(node).contains("static ");
let separator = if is_static { "." } else { "#" };
let id = self.make_id(&name, separator);
let doc_comment = self.find_preceding_comment(node);
let parsed_doc = doc_comment.as_ref().map(|c| parse_jsdoc(c));
let prop_type = node
.child_by_field_name("type")
.map(|n| self.parse_type_node(n));
let default = node
.child_by_field_name("value")
.map(|n| self.node_text(n).to_string());
let optional = self.node_text(node).contains('?');
let readonly = self.node_text(node).contains("readonly");
Some(Property {
base: BaseEntity {
id,
name,
location: Some(self.node_location(node)),
docs: parsed_doc.as_ref().map(|d| d.to_documentation()),
visibility: self.parse_visibility(node),
is_private_api: parsed_doc.as_ref().and_then(|d| {
if d.is_private {
Some(true)
} else {
None
}
}),
},
prop_type,
default,
readonly: if readonly { Some(true) } else { None },
is_static: if is_static { Some(true) } else { None },
optional: if optional { Some(true) } else { None },
getter: None,
setter: None,
})
}
fn parse_accessor(&mut self, node: Node, is_getter: bool) -> Option<Property> {
let name_node = node.child_by_field_name("name")?;
let name = self.node_text(name_node).to_string();
let is_static = self.node_text(node).contains("static ");
let separator = if is_static { "." } else { "#" };
let id = self.make_id(&name, separator);
let doc_comment = self.find_preceding_comment(node);
let parsed_doc = doc_comment.as_ref().map(|c| parse_jsdoc(c));
let prop_type = if is_getter {
node.child_by_field_name("return_type")
.map(|n| self.parse_type_node(n))
} else {
let first_param = node.child_by_field_name("parameters").and_then(|params| {
let mut cursor = params.walk();
let children: Vec<_> = params.named_children(&mut cursor).collect();
children.into_iter().next()
});
first_param
.and_then(|param| param.child_by_field_name("type"))
.map(|n| self.parse_type_node(n))
};
Some(Property {
base: BaseEntity {
id,
name,
location: Some(self.node_location(node)),
docs: parsed_doc.as_ref().map(|d| d.to_documentation()),
visibility: self.parse_visibility(node),
is_private_api: parsed_doc.as_ref().and_then(|d| {
if d.is_private {
Some(true)
} else {
None
}
}),
},
prop_type,
default: None,
readonly: None,
is_static: if is_static { Some(true) } else { None },
optional: None,
getter: None,
setter: None,
})
}
fn parse_type_parameters(&self, node: Node) -> Vec<TypeParameter> {
let mut type_params = Vec::new();
if let Some(params_node) = node.child_by_field_name("type_parameters") {
let mut cursor = params_node.walk();
for child in params_node.named_children(&mut cursor) {
if child.kind() == "type_parameter" {
let name = child
.child_by_field_name("name")
.map(|n| self.node_text(n).to_string())
.unwrap_or_default();
let constraint = child
.child_by_field_name("constraint")
.map(|n| self.parse_type_node(n));
let default = child
.child_by_field_name("value")
.map(|n| self.parse_type_node(n));
type_params.push(TypeParameter {
name,
constraint,
default,
description: None,
});
}
}
}
type_params
}
fn parse_parameters(&self, node: Node, doc: Option<&ParsedDocComment>) -> Vec<Parameter> {
let mut params = Vec::new();
if let Some(params_node) = node.child_by_field_name("parameters") {
let mut cursor = params_node.walk();
for child in params_node.named_children(&mut cursor) {
match child.kind() {
"required_parameter" | "optional_parameter" => {
let name = child
.child_by_field_name("pattern")
.or_else(|| child.child_by_field_name("name"))
.map(|n| self.node_text(n).to_string())
.unwrap_or_default();
let param_type = child
.child_by_field_name("type")
.map(|n| self.parse_type_node(n));
let default = child
.child_by_field_name("value")
.map(|n| self.node_text(n).to_string());
let optional = child.kind() == "optional_parameter"
|| self.node_text(child).contains('?');
let description = doc
.and_then(|d| d.params.iter().find(|p| p.name == name))
.and_then(|p| p.description.clone());
params.push(Parameter {
name,
param_type,
description,
default,
optional: if optional { Some(true) } else { None },
rest: None,
options: Vec::new(),
});
}
"rest_parameter" => {
let name = child
.child_by_field_name("name")
.map(|n| self.node_text(n).to_string())
.unwrap_or_else(|| "args".to_string());
let param_type = child
.child_by_field_name("type")
.map(|n| self.parse_type_node(n));
params.push(Parameter {
name,
param_type,
description: None,
default: None,
optional: None,
rest: Some(true),
options: Vec::new(),
});
}
_ => {}
}
}
}
params
}
fn parse_return_type(
&self,
node: Node,
doc: Option<&ParsedDocComment>,
) -> Option<TypeReference> {
node.child_by_field_name("return_type")
.map(|n| self.parse_type_node(n))
.or_else(|| doc.and_then(|d| d.return_type()))
}
fn parse_type_node(&self, node: Node) -> TypeReference {
let text = self.node_text(node);
let text = text.strip_prefix(':').unwrap_or(text).trim();
parse_type_string(text)
}
fn parse_visibility(&self, node: Node) -> Option<Visibility> {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "accessibility_modifier" {
let modifier = self.node_text(child);
return match modifier {
"private" => Some(Visibility::Private),
"protected" => Some(Visibility::Protected),
"public" => Some(Visibility::Public),
_ => Some(Visibility::Public),
};
}
}
Some(Visibility::Public)
}
fn generate_class_symbols(&self, class: &Class, parent: Option<&str>) -> Vec<SymbolEntry> {
let mut symbols = vec![SymbolEntry {
id: class.base.id.clone(),
name: class.base.name.clone(),
qualified_name: class.base.id.clone(),
kind: "class".to_string(),
parent: parent.map(|s| s.to_string()),
file: class.base.location.as_ref().map(|l| l.file.clone()),
line: class.base.location.as_ref().map(|l| l.line),
signature: self.format_class_signature(class),
}];
let parent_id = &class.base.id;
if let Some(ctor) = &class.constructor {
symbols.push(self.callable_to_symbol(ctor, Some(parent_id)));
}
for prop in &class.properties {
symbols.push(self.property_to_symbol(prop, Some(parent_id)));
}
for prop in &class.static_properties {
symbols.push(self.property_to_symbol(prop, Some(parent_id)));
}
for method in &class.methods {
symbols.push(self.callable_to_symbol(method, Some(parent_id)));
}
for method in &class.static_methods {
symbols.push(self.callable_to_symbol(method, Some(parent_id)));
}
symbols
}
fn generate_interface_symbols(
&self,
interface: &Interface,
parent: Option<&str>,
) -> Vec<SymbolEntry> {
let mut symbols = vec![SymbolEntry {
id: interface.base.id.clone(),
name: interface.base.name.clone(),
qualified_name: interface.base.id.clone(),
kind: "interface".to_string(),
parent: parent.map(|s| s.to_string()),
file: interface.base.location.as_ref().map(|l| l.file.clone()),
line: interface.base.location.as_ref().map(|l| l.line),
signature: None,
}];
let parent_id = &interface.base.id;
for prop in &interface.properties {
symbols.push(self.property_to_symbol(prop, Some(parent_id)));
}
for method in &interface.methods {
symbols.push(self.callable_to_symbol(method, Some(parent_id)));
}
symbols
}
fn format_class_signature(&self, class: &Class) -> Option<String> {
let mut sig = class.base.name.clone();
if !class.type_params.is_empty() {
let params: Vec<String> = class
.type_params
.iter()
.map(|p| {
let mut s = p.name.clone();
if let Some(c) = &p.constraint {
s.push_str(&format!(" extends {}", c.name));
}
if let Some(d) = &p.default {
s.push_str(&format!(" = {}", d.name));
}
s
})
.collect();
sig.push_str(&format!("<{}>", params.join(", ")));
}
if let Some(ext) = &class.extends {
sig.push_str(&format!(" extends {}", ext.name));
}
Some(sig)
}
fn callable_to_symbol(&self, callable: &Callable, parent: Option<&str>) -> SymbolEntry {
let param_names: Vec<String> = callable
.params
.iter()
.map(|p| {
let mut s = p.name.clone();
if p.optional == Some(true) {
s.push('?');
}
s
})
.collect();
let signature = format!("{}({})", callable.base.name, param_names.join(", "));
SymbolEntry {
id: callable.base.id.clone(),
name: callable.base.name.clone(),
qualified_name: callable.base.id.clone(),
kind: if callable.base.name == "constructor" {
"constructor"
} else {
"method"
}
.to_string(),
parent: parent.map(|s| s.to_string()),
file: callable.base.location.as_ref().map(|l| l.file.clone()),
line: callable.base.location.as_ref().map(|l| l.line),
signature: Some(signature),
}
}
fn property_to_symbol(&self, prop: &Property, parent: Option<&str>) -> SymbolEntry {
SymbolEntry {
id: prop.base.id.clone(),
name: prop.base.name.clone(),
qualified_name: prop.base.id.clone(),
kind: "property".to_string(),
parent: parent.map(|s| s.to_string()),
file: prop.base.location.as_ref().map(|l| l.file.clone()),
line: prop.base.location.as_ref().map(|l| l.line),
signature: None,
}
}
}
impl Parser for TypeScriptParser {
fn language(&self) -> Language {
Language::TypeScript
}
fn parse_file(&mut self, path: &Path, content: &str) -> Result<Vec<Entity>> {
self.file_path = path.to_string_lossy().to_string();
self.context_stack.clear();
let tree = self.parse(content)?;
let root = tree.root_node();
let mut entities = Vec::new();
let mut cursor = root.walk();
for child in root.named_children(&mut cursor) {
match child.kind() {
"class_declaration" => {
if let Some(class) = self.parse_class(child) {
entities.push(Entity::Class(class));
}
}
"interface_declaration" => {
if let Some(interface) = self.parse_interface(child) {
entities.push(Entity::Interface(interface));
}
}
"type_alias_declaration" => {
if let Some(type_alias) = self.parse_type_alias(child) {
entities.push(Entity::TypeAlias(type_alias));
}
}
"enum_declaration" => {
if let Some(enum_decl) = self.parse_enum(child) {
entities.push(Entity::Enum(enum_decl));
}
}
"export_statement" => {
let mut export_cursor = child.walk();
for export_child in child.named_children(&mut export_cursor) {
match export_child.kind() {
"class_declaration" => {
if let Some(class) = self.parse_class(export_child) {
entities.push(Entity::Class(class));
}
}
"interface_declaration" => {
if let Some(interface) = self.parse_interface(export_child) {
entities.push(Entity::Interface(interface));
}
}
"type_alias_declaration" => {
if let Some(type_alias) = self.parse_type_alias(export_child) {
entities.push(Entity::TypeAlias(type_alias));
}
}
"enum_declaration" => {
if let Some(enum_decl) = self.parse_enum(export_child) {
entities.push(Entity::Enum(enum_decl));
}
}
_ => {}
}
}
}
_ => {}
}
}
Ok(entities)
}
fn generate_symbols(&self, entities: &[Entity], parent: Option<&str>) -> Vec<SymbolEntry> {
let mut symbols = Vec::new();
for entity in entities {
match entity {
Entity::Class(c) => {
symbols.extend(self.generate_class_symbols(c, parent));
}
Entity::Interface(i) => {
symbols.extend(self.generate_interface_symbols(i, parent));
}
Entity::TypeAlias(t) => {
symbols.push(SymbolEntry {
id: t.base.id.clone(),
name: t.base.name.clone(),
qualified_name: t.base.id.clone(),
kind: "type".to_string(),
parent: parent.map(|s| s.to_string()),
file: t.base.location.as_ref().map(|l| l.file.clone()),
line: t.base.location.as_ref().map(|l| l.line),
signature: None,
});
}
Entity::Enum(e) => {
symbols.push(SymbolEntry {
id: e.base.id.clone(),
name: e.base.name.clone(),
qualified_name: e.base.id.clone(),
kind: "enum".to_string(),
parent: parent.map(|s| s.to_string()),
file: e.base.location.as_ref().map(|l| l.file.clone()),
line: e.base.location.as_ref().map(|l| l.line),
signature: None,
});
}
_ => {}
}
}
symbols
}
}