sdivi_lang_javascript/lib.rs
1//! JavaScript language adapter for sdivi-rust.
2//!
3//! Implements [`sdivi_parsing::adapter::LanguageAdapter`] for `.js` and `.mjs`
4//! source files using the `tree-sitter-javascript` grammar.
5//!
6//! # Thread safety
7//!
8//! `tree_sitter::Parser` is not `Send`. Parsers are stored in `thread_local!`
9//! storage so that `JavaScriptAdapter` itself can be `Send + Sync` and
10//! participate in rayon parallel parsing.
11
12mod extract;
13
14use std::cell::RefCell;
15use std::path::Path;
16
17use sdivi_parsing::adapter::LanguageAdapter;
18use sdivi_parsing::feature_record::FeatureRecord;
19
20use extract::{collect_hints, extract_exports, extract_imports, extract_signatures};
21
22thread_local! {
23 static PARSER: RefCell<tree_sitter::Parser> = RefCell::new({
24 let mut p = tree_sitter::Parser::new();
25 p.set_language(&tree_sitter_javascript::language())
26 .expect("tree-sitter-javascript grammar failed to load");
27 p
28 });
29}
30
31/// Language adapter for JavaScript source files.
32///
33/// Parses `.js` and `.mjs` files with the `tree-sitter-javascript` grammar
34/// and extracts:
35/// - `imports` from `import` statements
36/// - `exports` from `export` statements at module scope
37/// - `signatures` from function and method declarations
38/// - `pattern_hints` for the patterns stage
39///
40/// # Examples
41///
42/// ```rust
43/// use sdivi_lang_javascript::JavaScriptAdapter;
44/// use sdivi_parsing::adapter::LanguageAdapter;
45///
46/// let adapter = JavaScriptAdapter;
47/// assert_eq!(adapter.language_name(), "javascript");
48/// assert!(adapter.file_extensions().contains(&".js"));
49/// ```
50pub struct JavaScriptAdapter;
51
52impl LanguageAdapter for JavaScriptAdapter {
53 fn language_name(&self) -> &'static str {
54 "javascript"
55 }
56
57 fn file_extensions(&self) -> &[&'static str] {
58 &[".js", ".mjs"]
59 }
60
61 /// Parses `content` and returns a [`FeatureRecord`].
62 ///
63 /// The tree-sitter CST is created, traversed, and **dropped** before this
64 /// method returns. No tree-sitter type escapes into the returned record.
65 fn parse_file(&self, path: &Path, content: String) -> FeatureRecord {
66 let source = content.as_bytes();
67
68 let (imports, exports, signatures, pattern_hints) = PARSER.with(|cell| {
69 let mut parser = cell.borrow_mut();
70 let tree = parser
71 .parse(source, None)
72 .expect("tree-sitter-javascript failed to parse");
73 let root = tree.root_node();
74 let imports = extract_imports(root, source);
75 let exports = extract_exports(root, source);
76 let signatures = extract_signatures(root, source);
77 let hints = collect_hints(root, source);
78 (imports, exports, signatures, hints)
79 });
80
81 FeatureRecord {
82 path: path.to_path_buf(),
83 language: "javascript".to_string(),
84 imports,
85 exports,
86 signatures,
87 pattern_hints,
88 }
89 }
90}