Skip to main content

git_topology/chunking/
parser.rs

1use anyhow::{Context, Result};
2use tree_sitter::Parser;
3
4use super::languages::SupportedLanguage;
5use super::CodeChunk;
6
7const BODY_NODE_TYPES: &[&str] = &[
8    "block",
9    "statement_block",
10    "compound_statement",
11    "class_body",
12    "enum_body",
13    "field_declaration_list",
14    "declaration_list",
15];
16
17const CHUNK_NODE_TYPES: &[&str] = &[
18    "function_item",
19    "function_declaration",
20    "function_definition",
21    "method_declaration",
22    "method_definition",
23    "class_declaration",
24    "class_definition",
25    "impl_item",
26    "struct_item",
27    "enum_item",
28    "trait_item",
29];
30
31pub fn extract_signature(text: &str, language: SupportedLanguage) -> String {
32    let mut parser = tree_sitter::Parser::new();
33    let ts_language = language.tree_sitter_language();
34    if parser.set_language(&ts_language).is_err() {
35        return first_line_fallback(text);
36    }
37
38    let tree = match parser.parse(text, None) {
39        Some(t) => t,
40        None => return first_line_fallback(text),
41    };
42
43    let root = tree.root_node();
44    let mut cursor = root.walk();
45
46    let top = root
47        .children(&mut cursor)
48        .find(|n| !n.is_extra() && n.child_count() > 0);
49
50    let node = match top {
51        Some(n) => n,
52        None => return first_line_fallback(text),
53    };
54
55    let mut body_start_byte: Option<usize> = None;
56    let mut node_cursor = node.walk();
57    for child in node.children(&mut node_cursor) {
58        if BODY_NODE_TYPES.contains(&child.kind()) {
59            body_start_byte = Some(child.start_byte());
60            break;
61        }
62    }
63
64    match body_start_byte {
65        Some(body_start) => {
66            let sig = text[..body_start].trim_end_matches([' ', '\t', '{', ':']);
67            sig.trim_end().to_string()
68        }
69        None => text.trim_end_matches('{').trim_end().to_string(),
70    }
71}
72
73pub fn extract_name(text: &str, language: SupportedLanguage) -> String {
74    let mut parser = tree_sitter::Parser::new();
75    let ts_language = language.tree_sitter_language();
76    if parser.set_language(&ts_language).is_err() {
77        return first_line_fallback(text);
78    }
79
80    let tree = match parser.parse(text, None) {
81        Some(t) => t,
82        None => return first_line_fallback(text),
83    };
84
85    let root = tree.root_node();
86    let mut cursor = root.walk();
87
88    let top = root
89        .children(&mut cursor)
90        .find(|n| !n.is_extra() && n.child_count() > 0);
91
92    let node = match top {
93        Some(n) => n,
94        None => return first_line_fallback(text),
95    };
96
97    let mut node_cursor = node.walk();
98    for child in node.children(&mut node_cursor) {
99        if child.kind() == "name"
100            || child.kind() == "identifier"
101            || child.kind() == "type_identifier"
102        {
103            if let Ok(name) = child.utf8_text(text.as_bytes()) {
104                return name.to_string();
105            }
106        }
107    }
108
109    first_line_fallback(text)
110}
111
112fn first_line_fallback(text: &str) -> String {
113    text.lines()
114        .next()
115        .unwrap_or("")
116        .trim_end_matches(['{', ':'])
117        .trim_end()
118        .to_string()
119}
120
121pub fn parse(src: &str, language: SupportedLanguage) -> Option<tree_sitter::Tree> {
122    let mut parser = tree_sitter::Parser::new();
123    parser.set_language(&language.tree_sitter_language()).ok()?;
124    parser.parse(src, None)
125}
126
127pub fn parse_with_tree_sitter(text: &str, language: SupportedLanguage) -> Result<Vec<CodeChunk>> {
128    let mut parser = Parser::new();
129    let ts_language = language.tree_sitter_language();
130
131    parser
132        .set_language(&ts_language)
133        .context("Failed to set tree-sitter language")?;
134
135    let tree = parser
136        .parse(text, None)
137        .context("Failed to parse code with tree-sitter")?;
138
139    let root_node = tree.root_node();
140    let mut chunks = Vec::new();
141
142    walk_tree(text, root_node, &mut chunks);
143
144    if chunks.is_empty() {
145        chunks.push(CodeChunk {
146            text: text.to_string(),
147            start_line: 0,
148            end_line: text.lines().count(),
149        });
150    } else {
151        let first_start = chunks.iter().map(|c| c.start_line).min().unwrap_or(0);
152        if first_start > 0 {
153            let preamble: String = text
154                .lines()
155                .take(first_start)
156                .collect::<Vec<_>>()
157                .join("\n");
158            if !preamble.trim().is_empty() {
159                chunks.insert(
160                    0,
161                    CodeChunk {
162                        text: preamble,
163                        start_line: 0,
164                        end_line: first_start,
165                    },
166                );
167            }
168        }
169    }
170
171    Ok(chunks)
172}
173
174fn walk_tree(text: &str, node: tree_sitter::Node, chunks: &mut Vec<CodeChunk>) {
175    let node_kind = node.kind();
176
177    if CHUNK_NODE_TYPES.contains(&node_kind) || is_top_level_definition(&node) {
178        let start_line = node.start_position().row;
179        let end_line = node.end_position().row;
180        let start_byte = node.start_byte();
181        let end_byte = node.end_byte();
182
183        if let Some(chunk_text) = text.get(start_byte..end_byte) {
184            chunks.push(CodeChunk {
185                text: chunk_text.to_string(),
186                start_line,
187                end_line,
188            });
189            return;
190        }
191    }
192
193    let mut cursor = node.walk();
194    for child in node.children(&mut cursor) {
195        walk_tree(text, child, chunks);
196    }
197}
198
199fn is_top_level_definition(node: &tree_sitter::Node) -> bool {
200    matches!(
201        node.kind(),
202        "function"
203            | "class"
204            | "method"
205            | "struct"
206            | "enum"
207            | "trait"
208            | "impl"
209            | "interface"
210            | "type_alias"
211            | "const_declaration"
212    )
213}