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

igorls / context-builder / 22035105478

15 Feb 2026 11:46AM UTC coverage: 59.277% (-2.8%) from 62.112%
22035105478

push

github

igorls
release: v0.8.1

Bug fixes (Deep Think v6 review — 11 confirmed bugs):
- Fixed cache hash desync (4 missing fields)
- Fixed JS/TS arrow function body leak
- Fixed Python decorator erasure + is_method
- Fixed Rust tuple struct erasure
- Fixed C/C++ header prototype extraction
- Fixed C++ class inheritance byte-slicing
- Fixed JS/TS export signatures for lexical_declaration
- Added .jsx extension support

Dependency updates:
- tree-sitter: 0.24 → 0.26
- tree-sitter-rust: 0.23 → 0.24
- tree-sitter-javascript: 0.23 → 0.25
- tree-sitter-python: 0.23 → 0.25
- tree-sitter-go: 0.23 → 0.25
- tree-sitter-c: 0.23 → 0.24

93 of 292 new or added lines in 11 files covered. (31.85%)

29 existing lines in 3 files now uncovered.

1821 of 3072 relevant lines covered (59.28%)

4.07 hits per line

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

53.38
/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.
NEW
98
                let mut cursor = node.walk();
×
NEW
99
                for child in node.children(&mut cursor) {
×
NEW
100
                    if child.kind() == "function_definition" {
×
NEW
101
                        if let Some(sig) = self.extract_function_signature_with_context(
×
102
                            source, &child, Some(node),
103
                        ) {
NEW
104
                            signatures.push(sig);
×
105
                        }
106
                        // Don't recurse into children — we already handled the inner def
107
                        return;
NEW
108
                    } else if child.kind() == "class_definition" {
×
NEW
109
                        if let Some(sig) = self.extract_class_signature(source, &child) {
×
NEW
110
                            signatures.push(sig);
×
111
                        }
112
                        // Still recurse into class body for methods
NEW
113
                        let mut inner = child.walk();
×
NEW
114
                        for grandchild in child.children(&mut inner) {
×
NEW
115
                            self.extract_signatures_from_node(
×
116
                                source, &grandchild, _visibility, signatures,
117
                            );
118
                        }
119
                        return;
120
                    }
121
                }
122
            }
123
            "function_definition" => {
1✔
124
                if let Some(sig) = self.extract_function_signature_with_context(
2✔
125
                    source, node, None,
126
                ) {
127
                    signatures.push(sig);
1✔
128
                }
129
            }
130
            "class_definition" => {
1✔
131
                if let Some(sig) = self.extract_class_signature(source, node) {
1✔
132
                    signatures.push(sig);
1✔
133
                }
134
            }
135
            _ => {}
136
        }
137

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

361
    #[test]
362
    fn test_file_extensions() {
363
        assert!(PythonSupport.supports_extension("py"));
364
        assert!(PythonSupport.supports_extension("pyw"));
365
        assert!(!PythonSupport.supports_extension("rs"));
366
    }
367
}
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