use super::*;
impl TreeSitterExtractor {
pub(super) fn extract_variable(&mut self, node: &SyntaxNode) {
if let Some(extractor) = self.extractor {
let mut handled = false;
for variable in extractor.extract_variables(node, &self.source) {
handled = true;
if let Some(delegate) = variable.delegate_to_function {
let docstring = variable
.position_node
.as_ref()
.and_then(|pos| get_preceding_docstring(pos, &self.source))
.or_else(|| get_preceding_docstring(node, &self.source));
self.extract_function_with_extra(
&delegate,
Some(variable.name),
NodeExtra {
docstring,
is_exported: variable.is_exported,
..NodeExtra::default()
},
);
continue;
}
let pos = variable.position_node.as_ref().unwrap_or(node);
let extra = NodeExtra {
signature: variable.signature,
is_exported: variable.is_exported,
..NodeExtra::default()
};
if let Some(created) = self.create_node(variable.kind, &variable.name, pos, extra) {
self.nodes.push(created);
}
if let Some(value) = variable.visit_value {
self.visit_node(&value);
}
if let Some(object) = variable.object_literal_functions {
self.extract_object_literal_functions(&object);
}
}
if !handled {
self.extract_variable_fallback(node);
}
}
self.scan_fn_ref_subtree(node, 0);
}
pub(super) fn extract_variable_fallback(&mut self, node: &SyntaxNode) {
match self.language {
Language::Rust => self.extract_rust_variable(node),
Language::Go => self.extract_go_variable(node),
Language::Python => self.extract_python_variable(node),
Language::Java => self.extract_java_local_variable(node),
_ => {}
}
}
pub(super) fn extract_rust_variable(&mut self, node: &SyntaxNode) {
let kind = match node.node_type() {
"const_item" | "static_item" => NodeKind::Constant,
"let_declaration" => NodeKind::Variable,
_ => return,
};
let Some(name_node) = get_child_by_field(node, "name")
.or_else(|| get_child_by_field(node, "pattern"))
.or_else(|| node.named_child(0))
else {
return;
};
if name_node.node_type() != "identifier" {
return;
}
let name = get_node_text(name_node, &self.source);
if let Some(created) = self.create_node(kind, &name, node, NodeExtra::default()) {
self.nodes.push(created);
}
}
pub(super) fn extract_go_variable(&mut self, node: &SyntaxNode) {
if node.node_type() == "short_var_declaration" {
let Some(left) = get_child_by_field(node, "left").or_else(|| node.named_child(0))
else {
return;
};
let identifiers = if left.node_type() == "expression_list" {
left.named_children
.iter()
.filter(|child| child.node_type() == "identifier")
.collect::<Vec<_>>()
} else {
vec![left]
};
for identifier in identifiers {
let name = get_node_text(identifier, &self.source);
if let Some(created) =
self.create_node(NodeKind::Variable, &name, identifier, NodeExtra::default())
{
self.nodes.push(created);
}
}
return;
}
let kind = if node.node_type() == "const_declaration" {
NodeKind::Constant
} else {
NodeKind::Variable
};
for spec in node
.named_children
.iter()
.filter(|child| matches!(child.node_type(), "const_spec" | "var_spec"))
{
let Some(name_node) = spec.named_child(0) else {
continue;
};
if name_node.node_type() != "identifier" {
continue;
}
let name = get_node_text(name_node, &self.source);
if let Some(created) = self.create_node(kind, &name, spec, NodeExtra::default()) {
let id = node_id(&created);
self.nodes.push(created);
if let Some(value) = get_child_by_field(spec, "value") {
self.node_stack.push(id);
let function_id = self.node_stack.last().cloned().unwrap_or_default();
self.visit_function_body(value, &function_id);
self.node_stack.pop();
}
}
}
}
pub(super) fn extract_python_variable(&mut self, node: &SyntaxNode) {
if !get_node_text(node, &self.source).contains('=') {
return;
}
let Some(left) = get_child_by_field(node, "left").or_else(|| node.named_child(0)) else {
return;
};
if left.node_type() != "identifier" {
return;
}
let name = get_node_text(left, &self.source);
let kind = if name.chars().any(|ch| ch == '_' || ch.is_ascii_uppercase()) {
NodeKind::Constant
} else {
NodeKind::Variable
};
if let Some(created) = self.create_node(kind, &name, node, NodeExtra::default()) {
self.nodes.push(created);
}
}
pub(super) fn extract_java_local_variable(&mut self, node: &SyntaxNode) {
for declarator in node
.named_children
.iter()
.filter(|child| child.node_type() == "variable_declarator")
{
let Some(name_node) =
get_child_by_field(declarator, "name").or_else(|| declarator.named_child(0))
else {
continue;
};
let name = get_node_text(name_node, &self.source);
if let Some(created) =
self.create_node(NodeKind::Variable, &name, declarator, NodeExtra::default())
{
self.nodes.push(created);
}
}
}
pub(super) fn extract_type_alias(&mut self, node: &SyntaxNode) -> bool {
let Some(extractor) = self.extractor else {
return false;
};
let name = extract_name(node, &self.source, extractor);
let kind = extractor
.resolve_type_alias_kind(node, &self.source)
.unwrap_or(NodeKind::TypeAlias);
if let Some(created) = self.create_node(kind, &name, node, NodeExtra::default()) {
let id = node_id(&created);
self.nodes.push(created.clone());
if let Some(value) = get_child_by_field(node, "value").or_else(|| {
node.named_children.iter().rev().find(|child| {
!matches!(child.node_type(), "type_identifier" | "type_parameters")
})
}) {
if self.language == Language::Go && kind == NodeKind::Interface {
self.extract_inheritance(value, &id);
self.extract_go_interface_methods(value, &id);
return true;
}
self.extract_type_refs_from_subtree(value, &id);
if matches!(self.language, Language::TypeScript | Language::Tsx) {
self.extract_ts_tuple_contract_names(value, &created);
}
} else {
self.extract_type_refs_from_subtree(node, &id);
}
true
} else {
false
}
}
pub(super) fn extract_go_interface_methods(
&mut self,
interface_type: &SyntaxNode,
iface_id: &str,
) {
self.node_stack.push(iface_id.to_owned());
for method in &interface_type.named_children {
if !matches!(method.node_type(), "method_elem" | "method_spec") {
continue;
}
let Some(name_node) =
get_child_by_field(method, "name").or_else(|| method.named_child(0))
else {
continue;
};
let name = get_node_text(name_node, &self.source);
if name.is_empty() {
continue;
}
if let Some(created) = self.create_node(
NodeKind::Method,
&name,
method,
NodeExtra {
signature: self
.extractor
.and_then(|extractor| extractor.get_signature(method, &self.source)),
..NodeExtra::default()
},
) {
self.nodes.push(created);
}
}
self.node_stack.pop();
}
#[allow(dead_code)]
pub(super) fn extract_ts_type_alias_members(
&mut self,
value: &SyntaxNode,
type_alias_node: &Node,
) {
let _ = (value, type_alias_node);
}
pub(super) fn extract_ts_tuple_contract_names(
&mut self,
value: &SyntaxNode,
type_alias_node: &Node,
) {
let mut tuples = Vec::new();
collect_ts_tuple_types(value, 0, &mut tuples);
if tuples.is_empty() {
return;
}
self.node_stack.push(node_id(type_alias_node));
for tuple in tuples {
for entry in &tuple.named_children {
let Some(generic_entry) = ts_tuple_direct_generic_entry(entry) else {
continue;
};
let Some(type_args) = get_child_by_field(generic_entry, "type_arguments") else {
continue;
};
for arg in &type_args.named_children {
if arg.node_type() != "literal_type" {
continue;
}
let literal_text = arg
.named_child(0)
.filter(|child| child.node_type() == "string")
.map(|child| get_node_text(child, &self.source))
.unwrap_or_else(|| get_node_text(arg, &self.source));
let name = literal_text
.trim()
.trim_matches(['\'', '"', '`'])
.to_owned();
if !is_valid_ts_contract_name(&name) {
continue;
}
let signature =
collapse_whitespace(&get_node_text(generic_entry, &self.source))
.chars()
.take(120)
.collect::<String>();
if let Some(mut created) = self.create_node(
NodeKind::Method,
&name,
generic_entry,
NodeExtra {
signature: Some(signature),
..NodeExtra::default()
},
) {
created.qualified_name = format!("{}::{}", type_alias_node.name, name);
self.nodes.push(created);
}
}
}
}
self.node_stack.pop();
}
#[allow(dead_code)]
pub(super) fn is_ts_function_typed_property(&self, property_signature: &SyntaxNode) -> bool {
property_signature
.named_children
.iter()
.any(|child| child.node_type().contains("function"))
}
}