similarity_elixir/
elixir_parser.rs

1use similarity_core::language_parser::{
2    GenericFunctionDef, GenericTypeDef, Language, LanguageParser,
3};
4use similarity_core::tree::TreeNode;
5use std::error::Error;
6use std::rc::Rc;
7use tree_sitter::{Node, Parser};
8
9pub struct ElixirParser {
10    parser: Parser,
11}
12
13impl ElixirParser {
14    pub fn new() -> Result<Self, Box<dyn Error + Send + Sync>> {
15        let mut parser = Parser::new();
16        parser
17            .set_language(&tree_sitter_elixir::LANGUAGE.into())
18            .map_err(|e| format!("Failed to set Elixir language: {e:?}"))?;
19        Ok(Self { parser })
20    }
21
22    fn extract_functions_from_node(
23        &self,
24        node: Node,
25        source: &str,
26        functions: &mut Vec<GenericFunctionDef>,
27        module_name: Option<&str>,
28    ) {
29        let node_kind = node.kind();
30
31        // Check if this is a call node that could be a function or module
32        if node_kind == "call" {
33            if let Some(target_node) = node.child_by_field_name("target") {
34                if let Ok(target_text) = target_node.utf8_text(source.as_bytes()) {
35                    match target_text {
36                        // Function definitions
37                        "def" | "defp" | "defmacro" | "defmacrop" => {
38                            if let Some(func_def) =
39                                self.extract_function_definition(node, source, module_name)
40                            {
41                                functions.push(func_def);
42                            }
43                            return; // Don't traverse children
44                        }
45                        // Module definitions
46                        "defmodule" | "defprotocol" | "defimpl" => {
47                            // Extract module name
48                            let new_module_name = node
49                                .child_by_field_name("arguments")
50                                .and_then(|args| args.child(0))
51                                .and_then(|n| n.utf8_text(source.as_bytes()).ok())
52                                .unwrap_or("");
53
54                            // Process do_block
55                            let do_block = node.child(2).filter(|n| n.kind() == "do_block");
56                            if let Some(do_block) = do_block {
57                                for child in do_block.children(&mut do_block.walk()) {
58                                    self.extract_functions_from_node(
59                                        child,
60                                        source,
61                                        functions,
62                                        Some(new_module_name),
63                                    );
64                                }
65                            }
66                            return; // Don't traverse children normally
67                        }
68                        _ => {} // Continue normal traversal
69                    }
70                }
71            }
72        }
73
74        // Continue searching in children
75        for child in node.children(&mut node.walk()) {
76            self.extract_functions_from_node(child, source, functions, module_name);
77        }
78    }
79
80    fn extract_function_definition(
81        &self,
82        node: Node,
83        source: &str,
84        module_name: Option<&str>,
85    ) -> Option<GenericFunctionDef> {
86        // Extract function name from arguments -> first call -> target
87        let name_string = node
88            .child(1)
89            .filter(|n| n.kind() == "arguments")
90            .and_then(|args| args.child(0))
91            .and_then(|call_node| {
92                if call_node.kind() == "call" {
93                    call_node.child_by_field_name("target")
94                } else {
95                    None
96                }
97            })
98            .and_then(|n| n.utf8_text(source.as_bytes()).ok())
99            .map(String::from)?;
100
101        // Extract parameters
102        let params_node = node
103            .child(1)
104            .filter(|n| n.kind() == "arguments")
105            .and_then(|args| args.child(0))
106            .and_then(|call_node| {
107                if call_node.kind() == "call" {
108                    call_node.child(1).filter(|n| n.kind() == "arguments")
109                } else {
110                    None
111                }
112            });
113
114        // Extract do_block (may not exist for one-liner functions)
115        let body_node = node.child(2).filter(|n| n.kind() == "do_block");
116
117        let params = self.extract_parameters(params_node, source);
118
119        Some(GenericFunctionDef {
120            name: name_string,
121            start_line: node.start_position().row as u32 + 1,
122            end_line: node.end_position().row as u32 + 1,
123            body_start_line: body_node.map(|n| n.start_position().row as u32 + 1).unwrap_or(0),
124            body_end_line: body_node.map(|n| n.end_position().row as u32 + 1).unwrap_or(0),
125            parameters: params,
126            is_method: module_name.is_some(),
127            class_name: module_name.map(String::from),
128            is_async: false,
129            is_generator: false,
130            decorators: Vec::new(),
131        })
132    }
133
134    fn extract_parameters(&self, params_node: Option<Node>, source: &str) -> Vec<String> {
135        let Some(node) = params_node else {
136            return Vec::new();
137        };
138
139        let mut params = Vec::new();
140        for child in node.children(&mut node.walk()) {
141            if child.kind() == "identifier" {
142                if let Ok(param_text) = child.utf8_text(source.as_bytes()) {
143                    params.push(param_text.to_string());
144                }
145            }
146        }
147        params
148    }
149
150    fn build_tree_from_node(node: Node, source: &str, id: &mut usize) -> TreeNode {
151        let label = node.kind().to_string();
152        let value = if node.child_count() == 0 {
153            node.utf8_text(source.as_bytes()).ok().unwrap_or_default().to_string()
154        } else {
155            String::new()
156        };
157
158        let current_id = *id;
159        *id += 1;
160
161        let mut tree_node = TreeNode::new(label, value, current_id);
162
163        for child in node.children(&mut node.walk()) {
164            let child_node = Self::build_tree_from_node(child, source, id);
165            tree_node.add_child(Rc::new(child_node));
166        }
167
168        tree_node
169    }
170}
171
172impl LanguageParser for ElixirParser {
173    fn language(&self) -> Language {
174        Language::Unknown // TODO: Add Language::Elixir to core
175    }
176
177    fn parse(
178        &mut self,
179        source: &str,
180        _path: &str,
181    ) -> Result<Rc<TreeNode>, Box<dyn Error + Send + Sync>> {
182        let tree = self.parser.parse(source, None).ok_or("Failed to parse Elixir code")?;
183        let mut id = 0;
184        Ok(Rc::new(Self::build_tree_from_node(tree.root_node(), source, &mut id)))
185    }
186
187    fn extract_functions(
188        &mut self,
189        source: &str,
190        _path: &str,
191    ) -> Result<Vec<GenericFunctionDef>, Box<dyn Error + Send + Sync>> {
192        let tree = self.parser.parse(source, None).ok_or("Failed to parse Elixir code")?;
193
194        let mut functions = Vec::new();
195        self.extract_functions_from_node(tree.root_node(), source, &mut functions, None);
196        Ok(functions)
197    }
198
199    fn extract_types(
200        &mut self,
201        source: &str,
202        _path: &str,
203    ) -> Result<Vec<GenericTypeDef>, Box<dyn Error + Send + Sync>> {
204        let tree = self.parser.parse(source, None).ok_or("Failed to parse Elixir code")?;
205
206        let mut types = Vec::new();
207        Self::extract_types_from_node(tree.root_node(), source, &mut types);
208        Ok(types)
209    }
210}
211
212impl ElixirParser {
213    fn extract_types_from_node(node: Node, source: &str, types: &mut Vec<GenericTypeDef>) {
214        if node.kind() == "call" {
215            if let Some(target_node) = node.child_by_field_name("target") {
216                if let Ok(target_text) = target_node.utf8_text(source.as_bytes()) {
217                    if matches!(target_text, "defmodule" | "defprotocol" | "defimpl") {
218                        // Extract type name
219                        let name = node
220                            .child_by_field_name("arguments")
221                            .and_then(|args| args.child(0))
222                            .and_then(|n| n.utf8_text(source.as_bytes()).ok())
223                            .unwrap_or("");
224
225                        types.push(GenericTypeDef {
226                            name: name.to_string(),
227                            start_line: node.start_position().row as u32 + 1,
228                            end_line: node.end_position().row as u32 + 1,
229                            kind: match target_text {
230                                "defmodule" => "module",
231                                "defprotocol" => "protocol",
232                                "defimpl" => "implementation",
233                                _ => "unknown",
234                            }
235                            .to_string(),
236                            fields: Vec::new(),
237                        });
238                    }
239                }
240            }
241        }
242
243        // Continue searching in children
244        for child in node.children(&mut node.walk()) {
245            Self::extract_types_from_node(child, source, types);
246        }
247    }
248}