• 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

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
                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(|| match &params {
1✔
NEW
149
                Some(p) => format!("function {}({})", name, p),
×
NEW
150
                None => format!("function {}()", name),
×
151
            });
152

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

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

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

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

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

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

208
                    let full_signature = if let Some(body_start) = body_start {
1✔
209
                        // Slice from the parent node (lexical_declaration) to preserve `const`/`export`,
210
                        // down to the body start
211
                        source[node.start_byte()..body_start].trim_end().to_string()
×
212
                    } else {
213
                        // Expression-body arrow: `const add = (a, b) => a + b`
214
                        // Slice from parent through the `=>` token
215
                        let mut fn_cursor2 = fn_child.walk();
1✔
216
                        let arrow_end = fn_child
1✔
217
                            .children(&mut fn_cursor2)
1✔
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)
225
                            source[child.start_byte()..fn_child.start_byte()]
×
226
                                .trim_end()
227
                                .to_string()
228
                        }
229
                    };
230

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

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

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

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

306
    fn find_best_boundary(
×
307
        &self,
308
        cursor: &mut tree_sitter::TreeCursor,
309
        max_bytes: usize,
310
        best_end: &mut usize,
311
    ) {
312
        loop {
313
            let node = cursor.node();
×
314
            let end_byte = node.end_byte();
×
315

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

331
            if cursor.goto_first_child() {
×
332
                self.find_best_boundary(cursor, max_bytes, best_end);
×
333
                cursor.goto_parent();
×
334
            }
335

336
            if !cursor.goto_next_sibling() {
×
337
                break;
338
            }
339
        }
340
    }
341
}
342

343
#[cfg(test)]
344
mod tests {
345
    use super::*;
346

347
    #[test]
348
    fn test_extract_function_signature() {
349
        let source = r#"
350
function hello(name) {
351
    return `Hello, ${name}!`;
352
}
353

354
const add = (a, b) => a + b;
355
"#;
356

357
        let signatures = JavaScriptSupport.extract_signatures(source, Visibility::All);
358
        assert!(!signatures.is_empty());
359

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

368
    #[test]
369
    fn test_extract_class_signature() {
370
        let source = r#"
371
class User {
372
    constructor(name) {
373
        this.name = name;
374
    }
375
}
376
}
377
"#;
378

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

388
    #[test]
389
    fn test_file_extensions() {
390
        assert!(JavaScriptSupport.supports_extension("js"));
391
        assert!(JavaScriptSupport.supports_extension("mjs"));
392
        assert!(!JavaScriptSupport.supports_extension("ts"));
393
    }
394
}
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