• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

igorls / context-builder / 22042957845

15 Feb 2026 08:59PM UTC coverage: 79.739% (+0.05%) from 79.688%
22042957845

push

github

igorls
style: cargo fmt

39 of 74 new or added lines in 10 files covered. (52.7%)

2 existing lines in 2 files now uncovered.

2448 of 3070 relevant lines covered (79.74%)

4.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.06
/src/tree_sitter/languages/python.rs
1
//! Python language support for tree-sitter.
2

3
#[cfg(feature = "tree-sitter-python")]
4
use tree_sitter::{Parser, Tree};
5

6
#[cfg(feature = "tree-sitter-python")]
7
use crate::tree_sitter::language_support::{
8
    CodeStructure, LanguageSupport, Signature, SignatureKind, Visibility,
9
    slice_signature_before_body,
10
};
11

12
pub struct PythonSupport;
13

14
#[cfg(feature = "tree-sitter-python")]
15
impl PythonSupport {
16
    fn get_language() -> tree_sitter::Language {
1✔
17
        tree_sitter_python::LANGUAGE.into()
1✔
18
    }
19
}
20

21
#[cfg(feature = "tree-sitter-python")]
22
impl LanguageSupport for PythonSupport {
23
    fn file_extensions(&self) -> &[&'static str] {
1✔
24
        &["py", "pyw"]
25
    }
26

27
    fn parse(&self, source: &str) -> Option<Tree> {
1✔
28
        let mut parser = Parser::new();
1✔
29
        parser.set_language(&Self::get_language()).ok()?;
2✔
30
        parser.parse(source, None)
1✔
31
    }
32

33
    fn extract_signatures(&self, source: &str, visibility: Visibility) -> Vec<Signature> {
1✔
34
        let tree = match self.parse(source) {
1✔
35
            Some(t) => t,
1✔
36
            None => return Vec::new(),
×
37
        };
38

39
        let root = tree.root_node();
1✔
40
        let mut signatures = Vec::new();
1✔
41

42
        self.extract_signatures_from_node(source, &root, visibility, &mut signatures);
1✔
43

44
        signatures.sort_by_key(|s| s.line_number);
3✔
45
        signatures
1✔
46
    }
47

48
    fn extract_structure(&self, source: &str) -> CodeStructure {
×
49
        let tree = match self.parse(source) {
×
50
            Some(t) => t,
×
51
            None => return CodeStructure::default(),
×
52
        };
53

54
        let root = tree.root_node();
×
55
        let mut structure = CodeStructure {
56
            total_lines: source.lines().count(),
×
57
            ..Default::default()
58
        };
59

60
        self.extract_structure_from_node(&root, &mut structure);
×
61
        structure
×
62
    }
63

64
    fn find_truncation_point(&self, source: &str, max_bytes: usize) -> usize {
×
65
        if source.len() <= max_bytes {
×
66
            return source.len();
×
67
        }
68

69
        let tree = match self.parse(source) {
×
70
            Some(t) => t,
×
71
            None => return max_bytes,
×
72
        };
73

74
        let root = tree.root_node();
×
75
        let mut best_end = 0;
×
76

77
        let mut cursor = root.walk();
×
78
        self.find_best_boundary(&mut cursor, max_bytes, &mut best_end);
×
79
        drop(cursor);
×
80

81
        if best_end == 0 { max_bytes } else { best_end }
×
82
    }
83
}
84

85
#[cfg(feature = "tree-sitter-python")]
86
impl PythonSupport {
87
    fn extract_signatures_from_node(
1✔
88
        &self,
89
        source: &str,
90
        node: &tree_sitter::Node,
91
        _visibility: Visibility,
92
        signatures: &mut Vec<Signature>,
93
    ) {
94
        match node.kind() {
1✔
95
            "decorated_definition" => {
1✔
96
                // Intercept decorated_definition to preserve decorators.
97
                // Find the inner function_definition or class_definition.
98
                let mut cursor = node.walk();
×
99
                for child in node.children(&mut cursor) {
×
100
                    if child.kind() == "function_definition" {
×
NEW
101
                        if let Some(sig) =
×
102
                            self.extract_function_signature_with_context(source, &child, Some(node))
103
                        {
UNCOV
104
                            signatures.push(sig);
×
105
                        }
106
                        // Don't recurse into children — we already handled the inner def
107
                        return;
108
                    } else if child.kind() == "class_definition" {
×
109
                        if let Some(sig) = self.extract_class_signature(source, &child) {
×
110
                            signatures.push(sig);
×
111
                        }
112
                        // Still recurse into class body for methods
113
                        let mut inner = child.walk();
×
114
                        for grandchild in child.children(&mut inner) {
×
115
                            self.extract_signatures_from_node(
×
116
                                source,
117
                                &grandchild,
118
                                _visibility,
119
                                signatures,
120
                            );
121
                        }
122
                        return;
123
                    }
124
                }
125
            }
126
            "function_definition" => {
1✔
127
                if let Some(sig) = self.extract_function_signature_with_context(source, node, None)
2✔
128
                {
129
                    signatures.push(sig);
1✔
130
                }
131
            }
132
            "class_definition" => {
1✔
133
                if let Some(sig) = self.extract_class_signature(source, node) {
1✔
134
                    signatures.push(sig);
1✔
135
                }
136
            }
137
            _ => {}
138
        }
139

140
        let mut cursor = node.walk();
1✔
141
        for child in node.children(&mut cursor) {
2✔
142
            self.extract_signatures_from_node(source, &child, _visibility, signatures);
2✔
143
        }
144
    }
145

146
    fn extract_structure_from_node(&self, node: &tree_sitter::Node, structure: &mut CodeStructure) {
×
147
        match node.kind() {
×
148
            "function_definition" => structure.functions += 1,
×
149
            "class_definition" => structure.classes += 1,
×
150
            "import_statement" | "import_from_statement" => {
×
151
                structure.imports.push("import".to_string());
×
152
            }
153
            _ => {}
154
        }
155

156
        let mut cursor = node.walk();
×
157
        for child in node.children(&mut cursor) {
×
158
            self.extract_structure_from_node(&child, structure);
×
159
        }
160
    }
161

162
    /// Extract function signature, optionally with a decorator context node.
163
    /// `context_node` is the `decorated_definition` parent, if any.
164
    fn extract_function_signature_with_context(
1✔
165
        &self,
166
        source: &str,
167
        node: &tree_sitter::Node,
168
        context_node: Option<&tree_sitter::Node>,
169
    ) -> Option<Signature> {
170
        let name = self.find_child_text(node, "identifier", source)?;
2✔
171
        let params = self.find_child_text(node, "parameters", source);
2✔
172

173
        // Walk up parent chain iteratively to detect methods.
174
        // Handles: function_definition -> block -> class_definition
175
        // And:     function_definition -> decorated_definition -> block -> class_definition
176
        let is_method = {
177
            let mut current = node.parent();
1✔
178
            let mut found = false;
1✔
179
            // Walk up at most 4 levels (enough for decorated methods in nested classes)
180
            for _ in 0..4 {
2✔
181
                match current {
1✔
182
                    Some(p) if p.kind() == "class_definition" => {
2✔
183
                        found = true;
1✔
184
                        break;
185
                    }
186
                    Some(p) => current = p.parent(),
2✔
187
                    None => break,
188
                }
189
            }
190
            found
1✔
191
        };
192
        let kind = if is_method {
2✔
193
            SignatureKind::Method
1✔
194
        } else {
195
            SignatureKind::Function
1✔
196
        };
197

198
        // If we have a decorator context, slice from there to preserve decorators.
199
        // Otherwise slice from the function_definition node.
200
        let slice_node = context_node.unwrap_or(node);
1✔
201
        let full_sig =
1✔
202
            slice_signature_before_body(source, slice_node, &["block"]).unwrap_or_else(|| {
203
                let mut sig = String::new();
×
204
                sig.push_str("def ");
×
205
                sig.push_str(&name);
×
206
                if let Some(p) = &params {
×
207
                    sig.push_str(p);
×
208
                } else {
209
                    sig.push_str("()");
×
210
                }
211
                sig
×
212
            });
213

214
        Some(Signature {
1✔
215
            kind,
1✔
216
            name,
1✔
217
            params,
1✔
218
            return_type: None, // Captured via byte-slicing in full_sig
1✔
219
            visibility: Visibility::All,
220
            line_number: slice_node.start_position().row + 1,
2✔
221
            full_signature: full_sig,
1✔
222
        })
223
    }
224

225
    fn extract_class_signature(&self, source: &str, node: &tree_sitter::Node) -> Option<Signature> {
1✔
226
        let name = self.find_child_text(node, "identifier", source)?;
2✔
227
        let bases = self.find_child_text(node, "argument_list", source);
2✔
228

229
        let mut full_sig = String::new();
1✔
230
        if let Some(decorators) = self.find_decorators(source, node) {
2✔
231
            full_sig.push_str(&decorators);
×
232
            full_sig.push('\n');
×
233
        }
234
        full_sig.push_str("class ");
1✔
235
        full_sig.push_str(&name);
1✔
236
        if let Some(b) = &bases {
1✔
237
            full_sig.push('(');
×
238
            full_sig.push_str(b);
×
239
            full_sig.push(')');
×
240
        }
241

242
        Some(Signature {
1✔
243
            kind: SignatureKind::Class,
244
            name,
1✔
245
            params: bases,
1✔
246
            return_type: None,
1✔
247
            visibility: Visibility::All,
248
            line_number: node.start_position().row + 1,
2✔
249
            full_signature: full_sig,
1✔
250
        })
251
    }
252

253
    fn find_decorators(&self, source: &str, node: &tree_sitter::Node) -> Option<String> {
1✔
254
        let parent = node.parent()?;
1✔
255
        let mut cursor = parent.walk();
1✔
256
        let mut decorators = Vec::new();
1✔
257

258
        for sibling in parent.children(&mut cursor) {
2✔
259
            if sibling.kind() == "decorator"
2✔
260
                && sibling.end_position().row == node.start_position().row.saturating_sub(1)
×
261
            {
262
                let text = &source[sibling.start_byte()..sibling.end_byte()];
×
263
                decorators.push(text.to_string());
×
264
            }
265
        }
266

267
        if decorators.is_empty() {
2✔
268
            None
1✔
269
        } else {
270
            Some(decorators.join("\n"))
×
271
        }
272
    }
273

274
    fn find_child_text(
1✔
275
        &self,
276
        node: &tree_sitter::Node,
277
        kind: &str,
278
        source: &str,
279
    ) -> Option<String> {
280
        let mut cursor = node.walk();
1✔
281
        for child in node.children(&mut cursor) {
2✔
282
            if child.kind() == kind {
2✔
283
                return Some(source[child.start_byte()..child.end_byte()].to_string());
1✔
284
            }
285
        }
286
        None
1✔
287
    }
288

289
    fn find_best_boundary(
×
290
        &self,
291
        cursor: &mut tree_sitter::TreeCursor,
292
        max_bytes: usize,
293
        best_end: &mut usize,
294
    ) {
295
        loop {
296
            let node = cursor.node();
×
297
            let end_byte = node.end_byte();
×
298

299
            if end_byte <= max_bytes && end_byte > *best_end {
×
300
                let is_item = matches!(
×
301
                    node.kind(),
×
302
                    "function_definition" | "class_definition" | "decorated_definition"
303
                );
304
                if is_item {
×
305
                    *best_end = end_byte;
×
306
                }
307
            }
308

309
            if cursor.goto_first_child() {
×
310
                self.find_best_boundary(cursor, max_bytes, best_end);
×
311
                cursor.goto_parent();
×
312
            }
313

314
            if !cursor.goto_next_sibling() {
×
315
                break;
316
            }
317
        }
318
    }
319
}
320

321
#[cfg(test)]
322
mod tests {
323
    use super::*;
324

325
    #[test]
326
    fn test_extract_function_signature() {
327
        let source = r#"
328
def hello(name):
329
    return f"Hello, {name}!"
330

331
def add(a: int, b: int) -> int:
332
    return a + b
333
"#;
334

335
        let signatures = PythonSupport.extract_signatures(source, Visibility::All);
336
        assert!(!signatures.is_empty());
337

338
        let funcs: Vec<_> = signatures
339
            .iter()
340
            .filter(|s| matches!(s.kind, SignatureKind::Function | SignatureKind::Method))
341
            .collect();
342
        assert!(funcs.len() >= 2);
343
        assert_eq!(funcs[0].name, "hello");
344
    }
345

346
    #[test]
347
    fn test_extract_class_signature() {
348
        let source = r#"
349
class User:
350
    def __init__(self, name):
351
        self.name = name
352
"#;
353

354
        let signatures = PythonSupport.extract_signatures(source, Visibility::All);
355
        let classes: Vec<_> = signatures
356
            .iter()
357
            .filter(|s| s.kind == SignatureKind::Class)
358
            .collect();
359
        assert!(!classes.is_empty());
360
        assert_eq!(classes[0].name, "User");
361
    }
362

363
    #[test]
364
    fn test_file_extensions() {
365
        assert!(PythonSupport.supports_extension("py"));
366
        assert!(PythonSupport.supports_extension("pyw"));
367
        assert!(!PythonSupport.supports_extension("rs"));
368
    }
369
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc