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

igorls / context-builder / 22050078724

16 Feb 2026 04:24AM UTC coverage: 77.617% (-2.2%) from 79.772%
22050078724

push

github

igorls
release: v0.8.3

Bug fixes (Gemini Deep Think v6 clean benchmark):
- Fix output_folder auto-ignore silently hiding user content
- Fix diff_context_lines config ignored in auto-diff mode
- Fix double file I/O in process_file (reuse open fd)

Security hardening:
- install.sh: default to ~/.local/bin (no sudo needed)
- SKILL.md: promote cargo install, add SHA256 verification docs
- Soften -y flag guidance to require explicit path scoping

134 of 255 new or added lines in 6 files covered. (52.55%)

21 existing lines in 3 files now uncovered.

2410 of 3105 relevant lines covered (77.62%)

4.25 hits per line

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

54.09
/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
                // Extract signatures from exported declarations.
105
                // Return early — do NOT recurse into children of export_statement,
106
                // because extract_export_signatures already walks them.
UNCOV
107
                self.extract_export_signatures(source, node, signatures);
×
108
                return;
109
            }
110
            _ => {}
111
        }
112

113
        let mut cursor = node.walk();
1✔
114
        for child in node.children(&mut cursor) {
2✔
115
            self.extract_signatures_from_node(source, &child, _visibility, signatures);
2✔
116
        }
117
    }
118

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

136
        let mut cursor = node.walk();
×
137
        for child in node.children(&mut cursor) {
×
138
            self.extract_structure_from_node(&child, structure);
×
139
        }
140
    }
141

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

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

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

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

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

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

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

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

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

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

235
                    let name = self
236
                        .find_child_text(&child, "identifier", source)
1✔
237
                        .unwrap_or_default();
238

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

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

289
    fn find_child_text(
1✔
290
        &self,
291
        node: &tree_sitter::Node,
292
        kind: &str,
293
        source: &str,
294
    ) -> Option<String> {
295
        let mut cursor = node.walk();
1✔
296
        for child in node.children(&mut cursor) {
2✔
297
            if child.kind() == kind {
2✔
298
                return Some(source[child.start_byte()..child.end_byte()].to_string());
2✔
299
            }
300
            let mut nested_cursor = child.walk();
1✔
301
            for nested in child.children(&mut nested_cursor) {
2✔
302
                if nested.kind() == kind {
×
303
                    return Some(source[nested.start_byte()..nested.end_byte()].to_string());
×
304
                }
305
            }
306
        }
307
        None
×
308
    }
309

310
    fn find_best_boundary(
×
311
        &self,
312
        cursor: &mut tree_sitter::TreeCursor,
313
        max_bytes: usize,
314
        best_end: &mut usize,
315
    ) {
316
        loop {
317
            let node = cursor.node();
×
318
            let end_byte = node.end_byte();
×
319

320
            if end_byte <= max_bytes && end_byte > *best_end {
×
321
                let is_item = matches!(
×
322
                    node.kind(),
×
323
                    "function_declaration"
324
                        | "class_declaration"
325
                        | "method_definition"
326
                        | "export_statement"
327
                        | "variable_declaration"
328
                        | "lexical_declaration"
329
                );
330
                if is_item {
×
331
                    *best_end = end_byte;
×
332
                }
333
            }
334

335
            if cursor.goto_first_child() {
×
336
                self.find_best_boundary(cursor, max_bytes, best_end);
×
337
                cursor.goto_parent();
×
338
            }
339

340
            if !cursor.goto_next_sibling() {
×
341
                break;
342
            }
343
        }
344
    }
345
}
346

347
#[cfg(test)]
348
mod tests {
349
    use super::*;
350

351
    #[test]
352
    fn test_extract_function_signature() {
353
        let source = r#"
354
function hello(name) {
355
    return `Hello, ${name}!`;
356
}
357

358
const add = (a, b) => a + b;
359
"#;
360

361
        let signatures = JavaScriptSupport.extract_signatures(source, Visibility::All);
362
        assert!(!signatures.is_empty());
363

364
        let funcs: Vec<_> = signatures
365
            .iter()
366
            .filter(|s| s.kind == SignatureKind::Function)
367
            .collect();
368
        assert!(!funcs.is_empty());
369
        assert_eq!(funcs[0].name, "hello");
370
    }
371

372
    #[test]
373
    fn test_extract_class_signature() {
374
        let source = r#"
375
class User {
376
    constructor(name) {
377
        this.name = name;
378
    }
379
}
380
}
381
"#;
382

383
        let signatures = JavaScriptSupport.extract_signatures(source, Visibility::All);
384
        let classes: Vec<_> = signatures
385
            .iter()
386
            .filter(|s| s.kind == SignatureKind::Class)
387
            .collect();
388
        assert!(!classes.is_empty());
389
        assert_eq!(classes[0].name, "User");
390
    }
391

392
    #[test]
393
    fn test_file_extensions() {
394
        assert!(JavaScriptSupport.supports_extension("js"));
395
        assert!(JavaScriptSupport.supports_extension("mjs"));
396
        assert!(!JavaScriptSupport.supports_extension("ts"));
397
    }
398
}
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