use crate::indexer::languages::Language;
use tree_sitter::Node;
pub struct Lua {}
impl Language for Lua {
fn name(&self) -> &'static str {
"lua"
}
fn get_ts_language(&self) -> tree_sitter::Language {
tree_sitter_lua::LANGUAGE.into()
}
fn get_meaningful_kinds(&self) -> Vec<&'static str> {
vec![
"function_declaration",
"function_definition",
"local_function",
]
}
fn extract_symbols(&self, node: Node, contents: &str) -> Vec<String> {
let mut symbols = Vec::new();
match node.kind() {
"function_declaration" | "function_definition" | "local_function" => {
for child in node.children(&mut node.walk()) {
if child.kind() == "identifier" {
if let Ok(name) = child.utf8_text(contents.as_bytes()) {
symbols.push(name.to_string());
}
break;
}
}
for child in node.children(&mut node.walk()) {
if child.kind() == "block" {
self.extract_lua_block_symbols(child, contents, &mut symbols);
break;
}
}
}
_ => self.extract_identifiers(node, contents, &mut symbols),
}
symbols.sort();
symbols.dedup();
symbols
}
fn extract_identifiers(&self, node: Node, contents: &str, symbols: &mut Vec<String>) {
let kind = node.kind();
if kind == "identifier" {
if let Ok(text) = node.utf8_text(contents.as_bytes()) {
let t = text.trim();
if !t.is_empty() && self.is_valid_lua_identifier(t) {
symbols.push(t.to_string());
}
}
}
for child in node.children(&mut node.walk()) {
self.extract_identifiers(child, contents, symbols);
}
}
fn extract_imports_exports(&self, node: Node, contents: &str) -> (Vec<String>, Vec<String>) {
let mut imports = Vec::new();
let mut exports = Vec::new();
match node.kind() {
"function_call" => {
if let Some(function_name) = self.get_function_call_name(node, contents) {
if function_name == "require" {
if let Some(module_name) = self.get_require_module_name(node, contents) {
imports.push(module_name);
}
}
}
}
"return_statement" => {
if self.is_module_level_return(node) {
for child in node.children(&mut node.walk()) {
if child.kind() == "expression_list" {
self.extract_lua_exports_from_expression_list(
child,
contents,
&mut exports,
);
} else if child.kind() == "table_constructor" {
self.extract_lua_exports_from_table(child, contents, &mut exports);
} else if child.kind() == "identifier" {
if let Ok(name) = child.utf8_text(contents.as_bytes()) {
exports.push(name.to_string());
}
}
}
}
}
_ => {}
}
for child in node.children(&mut node.walk()) {
let (child_imports, child_exports) = self.extract_imports_exports(child, contents);
imports.extend(child_imports);
exports.extend(child_exports);
}
(imports, exports)
}
fn are_node_types_equivalent(&self, type1: &str, type2: &str) -> bool {
match (type1, type2) {
("function_declaration", "function_definition") => true,
("function_definition", "function_declaration") => true,
("function_declaration", "local_function") => true,
("local_function", "function_declaration") => true,
("function_definition", "local_function") => true,
("local_function", "function_definition") => true,
_ => type1 == type2,
}
}
fn get_node_type_description(&self, node_type: &str) -> &'static str {
match node_type {
"function_declaration" => "Function Declaration",
"function_definition" => "Function Definition",
"local_function" => "Local Function",
"assignment_statement" => "Variable Assignment",
"local_declaration" => "Local Variable Declaration",
"table_constructor" => "Table Constructor",
"field" => "Table Field",
"return_statement" => "Return Statement",
"function_call" => "Function Call",
"identifier" => "Identifier",
"block" => "Code Block",
"variable_list" => "Variable List",
"expression_list" => "Expression List",
_ => "Unknown Node Type",
}
}
fn get_file_extensions(&self) -> Vec<&'static str> {
vec!["lua"]
}
fn resolve_import(
&self,
import_path: &str,
source_file: &str,
all_files: &[String],
) -> Option<String> {
use crate::indexer::languages::resolution_utils::FileRegistry;
let registry = FileRegistry::new(all_files);
let base_dir = std::path::Path::new(source_file).parent()?;
if import_path.starts_with("./") || import_path.starts_with("../") {
let relative_path = base_dir.join(&import_path[2..]);
let lua_file = format!("{}.lua", relative_path.to_string_lossy());
if let Some(found) = registry.find_exact_file(&lua_file) {
return Some(found);
}
}
let module_parts: Vec<&str> = import_path.split('.').collect();
let mut current_path = base_dir.to_path_buf();
for (i, part) in module_parts.iter().enumerate() {
if i == module_parts.len() - 1 {
let file_path = current_path.join(format!("{}.lua", part));
if let Some(found) = registry.find_exact_file(&file_path.to_string_lossy()) {
return Some(found);
}
let init_path = current_path.join(part).join("init.lua");
if let Some(found) = registry.find_exact_file(&init_path.to_string_lossy()) {
return Some(found);
}
} else {
current_path = current_path.join(part);
}
}
None
}
}
impl Lua {
fn extract_lua_block_symbols(&self, node: Node, contents: &str, symbols: &mut Vec<String>) {
for child in node.children(&mut node.walk()) {
match child.kind() {
"local_declaration" | "assignment_statement" => {
for grandchild in child.children(&mut child.walk()) {
if grandchild.kind() == "variable_list" {
self.extract_lua_variable_list(grandchild, contents, symbols);
}
}
}
"function_declaration" | "function_definition" | "local_function" => {
for grandchild in child.children(&mut child.walk()) {
if grandchild.kind() == "identifier" {
if let Ok(name) = grandchild.utf8_text(contents.as_bytes()) {
symbols.push(name.to_string());
}
break;
}
}
}
_ => {
self.extract_lua_block_symbols(child, contents, symbols);
}
}
}
}
fn extract_lua_variable_list(&self, node: Node, contents: &str, symbols: &mut Vec<String>) {
for child in node.children(&mut node.walk()) {
if child.kind() == "identifier" {
if let Ok(name) = child.utf8_text(contents.as_bytes()) {
if self.is_valid_lua_identifier(name) {
symbols.push(name.to_string());
}
}
}
}
}
fn is_valid_lua_identifier(&self, text: &str) -> bool {
if text.is_empty() {
return false;
}
const LUA_KEYWORDS: &[&str] = &[
"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in",
"local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while",
];
if LUA_KEYWORDS.contains(&text) {
return false;
}
let first_char = text.chars().next().unwrap();
if !first_char.is_ascii_alphabetic() && first_char != '_' {
return false;
}
text.chars()
.skip(1)
.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
fn get_function_call_name(&self, node: Node, contents: &str) -> Option<String> {
for child in node.children(&mut node.walk()) {
if child.kind() == "identifier" {
if let Ok(name) = child.utf8_text(contents.as_bytes()) {
return Some(name.to_string());
}
}
}
None
}
fn get_require_module_name(&self, node: Node, contents: &str) -> Option<String> {
for child in node.children(&mut node.walk()) {
if child.kind() == "arguments" {
for grandchild in child.children(&mut child.walk()) {
if grandchild.kind() == "string" {
if let Ok(module_name) = grandchild.utf8_text(contents.as_bytes()) {
let cleaned = module_name.trim_matches('"').trim_matches('\'');
return Some(cleaned.to_string());
}
}
}
}
}
None
}
fn is_module_level_return(&self, node: Node) -> bool {
let mut current = node.parent();
while let Some(parent) = current {
match parent.kind() {
"function_declaration" | "function_definition" | "local_function" => {
return false; }
"chunk" => {
return true; }
_ => {
current = parent.parent();
}
}
}
true }
fn extract_lua_exports_from_expression_list(
&self,
node: Node,
contents: &str,
exports: &mut Vec<String>,
) {
for child in node.children(&mut node.walk()) {
match child.kind() {
"identifier" => {
if let Ok(name) = child.utf8_text(contents.as_bytes()) {
exports.push(name.to_string());
}
}
"table_constructor" => {
self.extract_lua_exports_from_table(child, contents, exports);
}
_ => {}
}
}
}
fn extract_lua_exports_from_table(
&self,
node: Node,
contents: &str,
exports: &mut Vec<String>,
) {
for child in node.children(&mut node.walk()) {
if child.kind() == "field" {
for grandchild in child.children(&mut child.walk()) {
if grandchild.kind() == "identifier" {
if let Ok(name) = grandchild.utf8_text(contents.as_bytes()) {
if self.is_valid_lua_identifier(name) {
exports.push(name.to_string());
}
}
}
}
}
}
}
}