• 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.5
/src/tree_sitter/languages/javascript.rs
1
//! JavaScript language support for tree-sitter.
2

3
use tree_sitter::{Parser, Tree};
4

5
use crate::tree_sitter::language_support::{
6
    CodeStructure, LanguageSupport, Signature, SignatureKind, Visibility,
7
    slice_signature_before_body,
8
};
9

10
pub struct JavaScriptSupport;
11

12
impl JavaScriptSupport {
13
    fn get_language() -> tree_sitter::Language {
1✔
14
        tree_sitter_javascript::LANGUAGE.into()
1✔
15
    }
16
}
17

18
impl LanguageSupport for JavaScriptSupport {
19
    fn file_extensions(&self) -> &[&'static str] {
1✔
20
        &["js", "mjs", "cjs", "jsx"]
21
    }
22

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

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

35
        let root = tree.root_node();
1✔
36
        let mut signatures = Vec::new();
1✔
37

38
        self.extract_signatures_from_node(source, &root, visibility, &mut signatures);
1✔
39

40
        signatures.sort_by_key(|s| s.line_number);
3✔
41
        signatures
1✔
42
    }
43

44
    fn extract_structure(&self, source: &str) -> CodeStructure {
×
45
        let tree = match self.parse(source) {
×
46
            Some(t) => t,
×
47
            None => return CodeStructure::default(),
×
48
        };
49

50
        let root = tree.root_node();
×
51
        let mut structure = CodeStructure {
52
            total_lines: source.lines().count(),
×
53
            ..Default::default()
54
        };
55

56
        self.extract_structure_from_node(&root, &mut structure);
×
57
        structure
×
58
    }
59

60
    fn find_truncation_point(&self, source: &str, max_bytes: usize) -> usize {
×
61
        if source.len() <= max_bytes {
×
62
            return source.len();
×
63
        }
64

65
        let tree = match self.parse(source) {
×
66
            Some(t) => t,
×
67
            None => return max_bytes,
×
68
        };
69

70
        let root = tree.root_node();
×
71
        let mut best_end = 0;
×
72

73
        let mut cursor = root.walk();
×
74
        self.find_best_boundary(&mut cursor, max_bytes, &mut best_end);
×
75
        drop(cursor);
×
76

77
        if best_end == 0 { max_bytes } else { best_end }
×
78
    }
79
}
80

81
impl JavaScriptSupport {
82
    fn extract_signatures_from_node(
1✔
83
        &self,
84
        source: &str,
85
        node: &tree_sitter::Node,
86
        _visibility: Visibility,
87
        signatures: &mut Vec<Signature>,
88
    ) {
89
        match node.kind() {
1✔
90
            "function_declaration" => {
1✔
91
                if let Some(sig) = self.extract_function_signature(source, node) {
2✔
92
                    signatures.push(sig);
1✔
93
                }
94
            }
95
            "class_declaration" => {
1✔
96
                if let Some(sig) = self.extract_class_signature(source, node) {
2✔
97
                    signatures.push(sig);
1✔
98
                }
99
            }
100
            "variable_declaration" | "lexical_declaration" => {
2✔
101
                self.extract_variable_declarations(source, node, signatures);
1✔
102
            }
103
            "export_statement" => {
1✔
104
                self.extract_export_signatures(source, node, signatures);
×
105
            }
106
            _ => {}
107
        }
108

109
        let mut cursor = node.walk();
1✔
110
        for child in node.children(&mut cursor) {
2✔
111
            self.extract_signatures_from_node(source, &child, _visibility, signatures);
2✔
112
        }
113
    }
114

115
    fn extract_structure_from_node(&self, node: &tree_sitter::Node, structure: &mut CodeStructure) {
×
116
        match node.kind() {
×
117
            "function_declaration" | "generator_function_declaration" | "function_expression" => {
×
118
                structure.functions += 1;
×
119
            }
120
            "class_declaration" | "class_expression" => {
×
121
                structure.classes += 1;
×
122
            }
123
            "import_statement" => {
×
124
                structure.imports.push("import".to_string());
×
125
            }
126
            "export_statement" => {
×
127
                structure.exports.push("export".to_string());
×
128
            }
129
            _ => {}
130
        }
131

132
        let mut cursor = node.walk();
×
133
        for child in node.children(&mut cursor) {
×
134
            self.extract_structure_from_node(&child, structure);
×
135
        }
136
    }
137

138
    fn extract_function_signature(
1✔
139
        &self,
140
        source: &str,
141
        node: &tree_sitter::Node,
142
    ) -> Option<Signature> {
143
        let name = self.find_child_text(node, "identifier", source)?;
2✔
144
        let params = self.find_child_text(node, "formal_parameters", source);
2✔
145

146
        // Use byte-slicing to preserve async, generator*, and complete parameter lists
147
        let full_sig = slice_signature_before_body(source, node, &["statement_block"])
1✔
148
            .unwrap_or_else(|| {
1✔
NEW
149
                match &params {
×
NEW
150
                    Some(p) => format!("function {}({})", name, p),
×
NEW
151
                    None => format!("function {}()", name),
×
152
                }
153
            });
154

155
        Some(Signature {
1✔
156
            kind: SignatureKind::Function,
157
            name,
1✔
158
            params,
1✔
159
            return_type: None,
1✔
160
            visibility: Visibility::All,
161
            line_number: node.start_position().row + 1,
2✔
162
            full_signature: full_sig,
1✔
163
        })
164
    }
165

166
    fn extract_class_signature(&self, source: &str, node: &tree_sitter::Node) -> Option<Signature> {
1✔
167
        let name = self.find_child_text(node, "identifier", source)?;
2✔
168

169
        // Use byte-slicing to preserve `extends` and other modifiers
170
        let full_sig = slice_signature_before_body(source, node, &["class_body"])
1✔
171
            .unwrap_or_else(|| format!("class {}", name));
1✔
172

173
        Some(Signature {
1✔
174
            kind: SignatureKind::Class,
175
            name,
1✔
176
            params: None,
1✔
177
            return_type: None,
1✔
178
            visibility: Visibility::All,
179
            line_number: node.start_position().row + 1,
2✔
180
            full_signature: full_sig,
1✔
181
        })
182
    }
183

184
    fn extract_variable_declarations(
1✔
185
        &self,
186
        source: &str,
187
        node: &tree_sitter::Node,
188
        signatures: &mut Vec<Signature>,
189
    ) {
190
        let mut cursor = node.walk();
1✔
191
        for child in node.children(&mut cursor) {
2✔
192
            if child.kind() == "variable_declarator" {
2✔
193
                // Check if this is an arrow function or regular function assignment
194
                let mut inner_cursor = child.walk();
1✔
195
                let fn_node = child.children(&mut inner_cursor).find(|c| {
3✔
196
                    c.kind() == "arrow_function" || c.kind() == "function"
1✔
197
                });
198

199
                if let Some(fn_child) = fn_node {
1✔
200
                    // Navigate INTO the arrow_function/function to find its body.
201
                    // statement_block is a child of arrow_function, NOT of variable_declarator.
202
                    let body_start = {
203
                        let mut fn_cursor = fn_child.walk();
1✔
204
                        fn_child.children(&mut fn_cursor)
2✔
205
                            .find(|c| c.kind() == "statement_block")
3✔
206
                            .map(|body| body.start_byte())
1✔
207
                    };
208

209
                    let full_signature = if let Some(body_start) = body_start {
1✔
210
                        // Slice from the parent node (lexical_declaration) to preserve `const`/`export`,
211
                        // down to the body start
NEW
212
                        source[node.start_byte()..body_start].trim_end().to_string()
×
213
                    } else {
214
                        // Expression-body arrow: `const add = (a, b) => a + b`
215
                        // Slice from parent through the `=>` token
216
                        let mut fn_cursor2 = fn_child.walk();
1✔
217
                        let arrow_end = fn_child.children(&mut fn_cursor2)
2✔
218
                            .find(|c| c.kind() == "=>")
3✔
219
                            .map(|arrow| arrow.end_byte());
3✔
220

221
                        if let Some(end) = arrow_end {
1✔
222
                            source[node.start_byte()..end].trim_end().to_string()
2✔
223
                        } else {
224
                            // Last resort: use declarator text only (name = params)
NEW
225
                            source[child.start_byte()..fn_child.start_byte()]
×
226
                                .trim_end().to_string()
227
                        }
228
                    };
229

230
                    let name = self.find_child_text(&child, "identifier", source)
1✔
231
                        .unwrap_or_default();
232

233
                    signatures.push(Signature {
1✔
234
                        kind: SignatureKind::Function,
235
                        name,
1✔
236
                        params: None, // Captured via byte-slicing
1✔
237
                        return_type: None,
1✔
238
                        visibility: Visibility::All,
239
                        line_number: child.start_position().row + 1,
2✔
240
                        full_signature,
1✔
241
                    });
242
                } else if let Some(name) = self.find_child_text(&child, "identifier", source) {
1✔
NEW
243
                    let full_signature = format!("const {}", &name);
×
NEW
244
                    signatures.push(Signature {
×
245
                        kind: SignatureKind::Constant,
NEW
246
                        name,
×
NEW
247
                        params: None,
×
NEW
248
                        return_type: None,
×
249
                        visibility: Visibility::All,
NEW
250
                        line_number: child.start_position().row + 1,
×
NEW
251
                        full_signature,
×
252
                    });
253
                }
254
            }
255
        }
256
    }
257

258
    fn extract_export_signatures(
×
259
        &self,
260
        source: &str,
261
        node: &tree_sitter::Node,
262
        signatures: &mut Vec<Signature>,
263
    ) {
264
        let mut cursor = node.walk();
×
265
        for child in node.children(&mut cursor) {
×
266
            if child.kind() == "function_declaration" {
×
267
                if let Some(sig) = self.extract_function_signature(source, &child) {
×
268
                    signatures.push(sig);
×
269
                }
270
            } else if child.kind() == "class_declaration"
×
271
                && let Some(sig) = self.extract_class_signature(source, &child)
×
272
            {
273
                signatures.push(sig);
×
NEW
274
            } else if child.kind() == "lexical_declaration" || child.kind() == "variable_declaration" {
×
275
                // Capture exported arrow functions: export const foo = () => {}
NEW
276
                self.extract_variable_declarations(source, &child, signatures);
×
277
            }
278
        }
279
    }
280

281
    fn find_child_text(
1✔
282
        &self,
283
        node: &tree_sitter::Node,
284
        kind: &str,
285
        source: &str,
286
    ) -> Option<String> {
287
        let mut cursor = node.walk();
1✔
288
        for child in node.children(&mut cursor) {
2✔
289
            if child.kind() == kind {
2✔
290
                return Some(source[child.start_byte()..child.end_byte()].to_string());
2✔
291
            }
292
            let mut nested_cursor = child.walk();
1✔
293
            for nested in child.children(&mut nested_cursor) {
2✔
294
                if nested.kind() == kind {
×
295
                    return Some(source[nested.start_byte()..nested.end_byte()].to_string());
×
296
                }
297
            }
298
        }
299
        None
×
300
    }
301

302
    fn find_best_boundary(
×
303
        &self,
304
        cursor: &mut tree_sitter::TreeCursor,
305
        max_bytes: usize,
306
        best_end: &mut usize,
307
    ) {
308
        loop {
309
            let node = cursor.node();
×
310
            let end_byte = node.end_byte();
×
311

312
            if end_byte <= max_bytes && end_byte > *best_end {
×
313
                let is_item = matches!(
×
314
                    node.kind(),
×
315
                    "function_declaration"
316
                        | "class_declaration"
317
                        | "method_definition"
318
                        | "export_statement"
319
                        | "variable_declaration"
320
                        | "lexical_declaration"
321
                );
322
                if is_item {
×
323
                    *best_end = end_byte;
×
324
                }
325
            }
326

327
            if cursor.goto_first_child() {
×
328
                self.find_best_boundary(cursor, max_bytes, best_end);
×
329
                cursor.goto_parent();
×
330
            }
331

332
            if !cursor.goto_next_sibling() {
×
333
                break;
334
            }
335
        }
336
    }
337
}
338

339
#[cfg(test)]
340
mod tests {
341
    use super::*;
342

343
    #[test]
344
    fn test_extract_function_signature() {
345
        let source = r#"
346
function hello(name) {
347
    return `Hello, ${name}!`;
348
}
349

350
const add = (a, b) => a + b;
351
"#;
352

353
        let signatures = JavaScriptSupport.extract_signatures(source, Visibility::All);
354
        assert!(!signatures.is_empty());
355

356
        let funcs: Vec<_> = signatures
357
            .iter()
358
            .filter(|s| s.kind == SignatureKind::Function)
359
            .collect();
360
        assert!(!funcs.is_empty());
361
        assert_eq!(funcs[0].name, "hello");
362
    }
363

364
    #[test]
365
    fn test_extract_class_signature() {
366
        let source = r#"
367
class User {
368
    constructor(name) {
369
        this.name = name;
370
    }
371
}
372
}
373
"#;
374

375
        let signatures = JavaScriptSupport.extract_signatures(source, Visibility::All);
376
        let classes: Vec<_> = signatures
377
            .iter()
378
            .filter(|s| s.kind == SignatureKind::Class)
379
            .collect();
380
        assert!(!classes.is_empty());
381
        assert_eq!(classes[0].name, "User");
382
    }
383

384
    #[test]
385
    fn test_file_extensions() {
386
        assert!(JavaScriptSupport.supports_extension("js"));
387
        assert!(JavaScriptSupport.supports_extension("mjs"));
388
        assert!(!JavaScriptSupport.supports_extension("ts"));
389
    }
390
}
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