use super::types::Language;
use tree_sitter::Node;
pub fn is_function_node(kind: &str, lang: Language) -> bool {
match lang {
Language::Python => kind == "function_definition",
Language::Rust => kind == "function_item",
Language::TypeScript | Language::JavaScript | Language::Vue | Language::Svelte => {
matches!(
kind,
"function_declaration" | "method_definition" | "arrow_function"
)
}
Language::Go => kind == "function_declaration" || kind == "method_declaration",
Language::Java => kind == "method_declaration" || kind == "constructor_declaration",
Language::C | Language::Cpp => kind == "function_definition",
Language::Ruby => kind == "method" || kind == "singleton_method",
Language::CSharp => kind == "method_declaration" || kind == "constructor_declaration",
Language::Kotlin => matches!(kind, "function_declaration" | "anonymous_function"),
Language::Swift => matches!(kind, "function_declaration" | "init_declaration"),
Language::Scala => matches!(kind, "function_definition" | "function_declaration"),
Language::Php => matches!(kind, "function_definition" | "method_declaration"),
Language::Lua => kind == "function_declaration",
Language::Elixir => matches!(kind, "call" | "anonymous_function"), Language::Haskell => kind == "function",
Language::Ocaml => matches!(kind, "let_binding" | "value_definition"),
Language::R => kind == "function_definition",
Language::Zig => kind == "FnProto" || kind == "fn_decl",
Language::Julia => matches!(kind, "function_definition" | "short_function_definition"),
Language::Sql => matches!(kind, "create_function_statement" | "create_procedure"),
_ => false,
}
}
pub fn is_class_node(kind: &str, lang: Language) -> bool {
match lang {
Language::Python => kind == "class_definition",
Language::Rust => matches!(
kind,
"impl_item" | "struct_item" | "enum_item" | "trait_item"
),
Language::TypeScript | Language::Vue | Language::Svelte => matches!(
kind,
"class_declaration"
| "interface_declaration"
| "type_alias_declaration"
| "enum_declaration"
),
Language::JavaScript => kind == "class_declaration",
Language::Go => kind == "type_declaration",
Language::Java => matches!(
kind,
"class_declaration" | "interface_declaration" | "enum_declaration"
),
Language::Cpp => matches!(
kind,
"class_specifier" | "struct_specifier" | "enum_specifier"
),
Language::Ruby => kind == "class" || kind == "module",
Language::CSharp => matches!(
kind,
"class_declaration"
| "interface_declaration"
| "enum_declaration"
| "struct_declaration"
),
Language::Kotlin => matches!(
kind,
"class_declaration" | "object_declaration" | "interface_declaration"
),
Language::Swift => matches!(
kind,
"class_declaration"
| "struct_declaration"
| "protocol_declaration"
| "enum_declaration"
),
Language::Scala => matches!(
kind,
"class_definition" | "object_definition" | "trait_definition"
),
Language::Php => matches!(
kind,
"class_declaration"
| "interface_declaration"
| "trait_declaration"
| "enum_declaration"
),
Language::Lua => false, Language::Elixir => kind == "call", Language::Haskell => matches!(kind, "type_alias" | "newtype" | "adt"),
Language::Ocaml => matches!(kind, "type_definition" | "module_definition"),
Language::R => false, Language::Zig => kind == "ContainerDecl", Language::Julia => matches!(kind, "struct_definition" | "abstract_definition"),
Language::Sql => matches!(
kind,
"create_table_statement" | "create_view_statement" | "create_index_statement"
),
Language::C => matches!(
kind,
"struct_specifier" | "union_specifier" | "enum_specifier"
),
Language::Css => matches!(
kind,
"rule_set" | "media_statement" | "keyframes_statement" | "supports_statement"
),
_ => false,
}
}
pub fn is_constant_node(kind: &str, lang: Language) -> bool {
match lang {
Language::Rust => matches!(kind, "const_item" | "static_item"),
Language::TypeScript | Language::JavaScript | Language::Vue | Language::Svelte => {
matches!(kind, "lexical_declaration" | "variable_declaration")
}
Language::Go => matches!(kind, "const_declaration" | "var_declaration"),
Language::C | Language::Cpp => kind == "declaration",
Language::Python => {
kind == "expression_statement"
}
Language::Kotlin => kind == "property_declaration",
Language::Swift => matches!(kind, "constant_declaration" | "variable_declaration"),
Language::Scala => matches!(kind, "val_definition" | "var_definition"),
Language::Php => kind == "const_declaration",
Language::Elixir => kind == "unary_operator", Language::Haskell => kind == "function", Language::Ocaml => kind == "let_binding",
Language::R => kind == "left_assignment" || kind == "equals_assignment", Language::Zig => kind == "VarDecl", Language::Julia => kind == "const_statement",
Language::Sql => false, Language::Css => matches!(
kind,
"import_statement" | "charset_statement" | "namespace_statement"
),
_ => false,
}
}
pub fn find_class_body(node: Node, lang: Language) -> Option<Node> {
match lang {
Language::Python => node.child_by_field_name("body"),
Language::Rust => node.child_by_field_name("body"),
Language::TypeScript | Language::JavaScript | Language::Vue | Language::Svelte => {
node.child_by_field_name("body")
}
Language::Java | Language::CSharp => node.child_by_field_name("body"),
Language::Go => node.child_by_field_name("type"),
Language::Cpp => {
for child in node.children(&mut node.walk()) {
if child.kind() == "field_declaration_list" {
return Some(child);
}
}
None
}
Language::Ruby => node.child_by_field_name("body"),
Language::Kotlin | Language::Swift | Language::Scala | Language::Php => {
node.child_by_field_name("body")
}
Language::Elixir => node.child_by_field_name("body"),
Language::Haskell | Language::Ocaml => node.child_by_field_name("body"),
Language::R => None, Language::Zig => node.child_by_field_name("body"),
Language::Julia => node.child_by_field_name("body"),
Language::Sql => None, Language::C => {
for child in node.children(&mut node.walk()) {
if child.kind() == "field_declaration_list" {
return Some(child);
}
}
None
}
Language::Css => {
let want = if node.kind() == "keyframes_statement" {
"keyframe_block_list"
} else {
"block"
};
node.children(&mut node.walk()).find(|c| c.kind() == want)
}
_ => None,
}
}
pub fn get_node_name(node: Node, bytes: &[u8], lang: Language) -> Option<String> {
let name_node = match lang {
Language::Python
| Language::Rust
| Language::Go
| Language::Java
| Language::Ruby
| Language::CSharp => node.child_by_field_name("name"),
Language::TypeScript | Language::JavaScript | Language::Vue | Language::Svelte => node
.child_by_field_name("name")
.or_else(|| node.child_by_field_name("property")),
Language::C | Language::Cpp => {
if matches!(
node.kind(),
"class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
) {
return node
.child_by_field_name("name")
.or_else(|| {
node.children(&mut node.walk())
.find(|child| child.kind() == "type_identifier")
})
.and_then(|n| n.utf8_text(bytes).ok().map(|s| s.to_string()));
}
node.child_by_field_name("declarator").and_then(|d| {
if d.kind() == "function_declarator" {
d.child_by_field_name("declarator")
} else {
Some(d)
}
})
}
Language::Kotlin
| Language::Swift
| Language::Scala
| Language::Php
| Language::Lua
| Language::Haskell
| Language::R
| Language::Zig
| Language::Julia
| Language::Sql => node.child_by_field_name("name"),
Language::Elixir => {
node.child_by_field_name("target")
.or_else(|| node.child_by_field_name("name"))
}
Language::Ocaml => node
.child_by_field_name("name")
.or_else(|| node.child_by_field_name("pattern")),
Language::Css => {
return get_css_unit_name(node, bytes);
}
_ => None,
};
name_node.and_then(|n| {
let text = n.utf8_text(bytes).ok()?;
if text.is_empty() {
None
} else {
Some(text.to_string())
}
})
}
fn get_css_unit_name(node: Node, bytes: &[u8]) -> Option<String> {
let kind = node.kind();
let text_of = |n: Node| -> Option<String> {
let t = n.utf8_text(bytes).ok()?;
let trimmed = t.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.split_whitespace().collect::<Vec<_>>().join(" "))
}
};
match kind {
"rule_set" => node
.children(&mut node.walk())
.find(|c| c.kind() == "selectors")
.and_then(text_of),
"keyframes_statement" => node
.children(&mut node.walk())
.find(|c| c.kind() == "keyframes_name")
.and_then(text_of)
.map(|n| format!("@keyframes {}", n)),
"media_statement" | "supports_statement" => {
let kw = if kind == "media_statement" {
"@media"
} else {
"@supports"
};
let query: Vec<String> = node
.children(&mut node.walk())
.filter(|c| {
matches!(
c.kind(),
"binary_query"
| "feature_query"
| "keyword_query"
| "parenthesized_query"
| "selector_query"
| "unary_query"
)
})
.filter_map(text_of)
.collect();
if query.is_empty() {
Some(kw.to_string())
} else {
Some(format!("{} {}", kw, query.join(" ")))
}
}
"import_statement" => Some("@import".to_string()),
"charset_statement" => Some("@charset".to_string()),
"namespace_statement" => Some("@namespace".to_string()),
_ => None,
}
}
pub fn find_start_with_attributes(node_start_line: usize, lines: &[&str], lang: Language) -> usize {
if node_start_line == 0 {
return 0;
}
let mut start = node_start_line;
for i in (0..node_start_line).rev() {
let line = lines.get(i).map(|s| s.trim()).unwrap_or("");
if line.is_empty() {
continue;
}
let is_attribute = match lang {
Language::Rust => {
line.starts_with("#[") || line.starts_with("#![") || line.starts_with("///")
}
Language::Python => line.starts_with('@'),
Language::Java | Language::Kotlin | Language::Scala => line.starts_with('@'),
Language::CSharp => line.starts_with('[') && line.ends_with(']'),
Language::TypeScript | Language::JavaScript | Language::Vue | Language::Svelte => {
line.starts_with('@') || line.starts_with("/**") || line.starts_with("*")
}
Language::Go => line.starts_with("//"),
_ => false,
};
if is_attribute {
start = i;
} else {
break;
}
}
start
}