Skip to main content

thread_language/
parsers.rs

1// SPDX-FileCopyrightText: 2022 Herrington Darkholme <2883231+HerringtonDarkholme@users.noreply.github.com>
2// SPDX-FileCopyrightText: 2025 Knitli Inc. <knitli@knit.li>
3// SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
4//
5// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
6
7//! Tree-sitter parser initialization and caching for all supported languages.
8//!
9//! Provides cached, zero-cost access to tree-sitter language parsers. Each parser is initialized once and cached using
10//! [`std::sync::OnceLock`] for thread-safe, lazy initialization.
11//!
12//! ## Feature Flags
13//!
14//! ### `all-parsers`
15//! When enabled (default), imports all tree-sitter parser crates and provides
16//! full parser functionality. Disable for WebAssembly builds where tree-sitter
17//! cannot be compiled.
18//!
19//! ### Individual Language Features
20//!
21//! Each language has its own feature flag, so you can enable whichever ones you need.
22//!
23//! ### `napi-environment`
24//! Disables tree-sitter parsing functionality because tree-sitter cannot build
25//! in a NAPI environment. NAPI (Node.js API) is used for building WASM
26//! modules for Node.js. (note: WASM builds for browser or other environments can support tree-sitter)
27//!
28//! #### Napi-compatible Languages
29//! You can use these languages in a NAPI environment:
30//! - You can use the `napi-compatible` flag as a shortcut to enable all napi-compatible languages.
31//! - `css`
32//! - `html`
33//! - `javascript`
34//! - `typescript`
35//! - `tsx`
36//!
37//! ## Parser Functions
38//!
39//! Each language has a corresponding `language_*()` function that returns a
40//! cached [`TSLanguage`] instance:
41//!
42//! ```rust
43//! use thread_language::parsers::{language_rust, language_javascript};
44//!
45//! let rust_lang = language_rust();
46//! let js_lang = language_javascript();
47//! ```
48//!
49//! ## Caching Strategy
50//!
51//! Parsers use [`std::sync::LazyLock`] for optimal performance:
52//! - First call initializes the parser
53//! - Subsequent calls return the cached instance
54//! - Thread-safe with no synchronization overhead after initialization
55
56#[cfg(all(
57    any(
58        feature = "all-parsers",
59        feature = "bash",
60        feature = "c",
61        feature = "cpp",
62        feature = "csharp",
63        feature = "css",
64        feature = "elixir",
65        feature = "go",
66        feature = "haskell",
67        feature = "hcl",
68        feature = "html",
69        feature = "java",
70        feature = "javascript",
71        feature = "json",
72        feature = "kotlin",
73        feature = "lua",
74        feature = "nix",
75        feature = "php",
76        feature = "python",
77        feature = "ruby",
78        feature = "rust",
79        feature = "scala",
80        feature = "solidity",
81        feature = "swift",
82        feature = "tsx",
83        feature = "typescript",
84        feature = "yaml"
85    ),
86    not(any(
87        feature = "napi-environment",
88        feature = "napi-compatible",
89        feature = "css-napi",
90        feature = "html-napi",
91        feature = "javascript-napi",
92        feature = "typescript-napi",
93        feature = "tsx-napi"
94    ))
95))]
96macro_rules! into_lang {
97    ($lang: ident, $field: ident) => {
98        $lang::$field.into()
99    };
100    ($lang: ident) => {
101        into_lang!($lang, LANGUAGE)
102    };
103}
104
105// Napi-environment specific macro
106#[cfg(all(
107    feature = "napi-environment",
108    any(
109        feature = "napi-compatible",
110        feature = "css-napi",
111        feature = "html-napi",
112        feature = "javascript-napi",
113        feature = "typescript-napi",
114        feature = "tsx-napi"
115    )
116))]
117macro_rules! into_lang {
118    ($lang: ident, $field: ident) => {
119        unimplemented!(
120            "tree-sitter parser is not implemented when feature flag [napi-environment] is on."
121        )
122    };
123    ($lang: ident) => {
124        into_lang!($lang, LANGUAGE)
125    };
126}
127
128// With TS-enabled, we can always use the `into_napi_lang!` macro
129// to convert the language into a NAPI-compatible type.
130// We just can't do it... in NAPI.
131#[cfg(all(
132    any(
133        feature = "all-parsers",
134        feature = "bash",
135        feature = "c",
136        feature = "cpp",
137        feature = "csharp",
138        feature = "css",
139        feature = "elixir",
140        feature = "go",
141        feature = "haskell",
142        feature = "hcl",
143        feature = "html",
144        feature = "java",
145        feature = "javascript",
146        feature = "json",
147        feature = "kotlin",
148        feature = "lua",
149        feature = "nix",
150        feature = "php",
151        feature = "python",
152        feature = "ruby",
153        feature = "rust",
154        feature = "scala",
155        feature = "solidity",
156        feature = "swift",
157        feature = "tsx",
158        feature = "typescript",
159        feature = "yaml"
160    ),
161    not(feature = "napi-environment")
162))]
163macro_rules! into_napi_lang {
164    ($lang: path) => {
165        $lang.into()
166    };
167}
168
169// Napi-environment specific macro
170#[cfg(any(
171    feature = "napi-environment",
172    feature = "napi-compatible",
173    feature = "css-napi",
174    feature = "html-napi",
175    feature = "javascript-napi",
176    feature = "typescript-napi",
177    feature = "tsx-napi"
178))]
179macro_rules! into_napi_lang {
180    ($lang: path) => {
181        $lang.into()
182    };
183}
184
185use std::sync::OnceLock;
186use thread_ast_engine::tree_sitter::TSLanguage;
187
188// Cached language instances for zero-cost repeated access
189#[cfg(any(feature = "bash", feature = "all-parsers"))]
190static BASH_LANG: OnceLock<TSLanguage> = OnceLock::new();
191#[cfg(any(feature = "c", feature = "all-parsers"))]
192static C_LANG: OnceLock<TSLanguage> = OnceLock::new();
193#[cfg(any(feature = "cpp", feature = "all-parsers"))]
194static CPP_LANG: OnceLock<TSLanguage> = OnceLock::new();
195#[cfg(any(feature = "csharp", feature = "all-parsers"))]
196static CSHARP_LANG: OnceLock<TSLanguage> = OnceLock::new();
197#[cfg(any(
198    feature = "css",
199    feature = "all-parsers",
200    feature = "css-napi",
201    feature = "napi-compatible"
202))]
203static CSS_LANG: OnceLock<TSLanguage> = OnceLock::new();
204#[cfg(any(feature = "elixir", feature = "all-parsers"))]
205static ELIXIR_LANG: OnceLock<TSLanguage> = OnceLock::new();
206#[cfg(any(feature = "go", feature = "all-parsers"))]
207static GO_LANG: OnceLock<TSLanguage> = OnceLock::new();
208#[cfg(any(feature = "haskell", feature = "all-parsers"))]
209static HASKELL_LANG: OnceLock<TSLanguage> = OnceLock::new();
210#[cfg(any(feature = "hcl", feature = "all-parsers"))]
211static HCL_LANG: OnceLock<TSLanguage> = OnceLock::new();
212#[cfg(any(
213    feature = "html",
214    feature = "all-parsers",
215    feature = "html-napi",
216    feature = "napi-compatible"
217))]
218static HTML_LANG: OnceLock<TSLanguage> = OnceLock::new();
219#[cfg(any(feature = "java", feature = "all-parsers"))]
220static JAVA_LANG: OnceLock<TSLanguage> = OnceLock::new();
221#[cfg(any(
222    feature = "javascript",
223    feature = "all-parsers",
224    feature = "javascript-napi",
225    feature = "napi-compatible"
226))]
227static JAVASCRIPT_LANG: OnceLock<TSLanguage> = OnceLock::new();
228#[cfg(any(feature = "json", feature = "all-parsers"))]
229static JSON_LANG: OnceLock<TSLanguage> = OnceLock::new();
230#[cfg(any(feature = "kotlin", feature = "all-parsers"))]
231static KOTLIN_LANG: OnceLock<TSLanguage> = OnceLock::new();
232#[cfg(any(feature = "lua", feature = "all-parsers"))]
233static LUA_LANG: OnceLock<TSLanguage> = OnceLock::new();
234#[cfg(any(feature = "nix", feature = "all-parsers"))]
235static NIX_LANG: OnceLock<TSLanguage> = OnceLock::new();
236#[cfg(any(feature = "php", feature = "all-parsers"))]
237static PHP_LANG: OnceLock<TSLanguage> = OnceLock::new();
238#[cfg(any(feature = "python", feature = "all-parsers"))]
239static PYTHON_LANG: OnceLock<TSLanguage> = OnceLock::new();
240#[cfg(any(feature = "ruby", feature = "all-parsers"))]
241static RUBY_LANG: OnceLock<TSLanguage> = OnceLock::new();
242#[cfg(any(feature = "rust", feature = "all-parsers"))]
243static RUST_LANG: OnceLock<TSLanguage> = OnceLock::new();
244#[cfg(any(feature = "scala", feature = "all-parsers"))]
245static SCALA_LANG: OnceLock<TSLanguage> = OnceLock::new();
246#[cfg(any(feature = "solidity", feature = "all-parsers"))]
247static SOLIDITY_LANG: OnceLock<TSLanguage> = OnceLock::new();
248#[cfg(any(feature = "swift", feature = "all-parsers"))]
249static SWIFT_LANG: OnceLock<TSLanguage> = OnceLock::new();
250#[cfg(any(
251    feature = "tsx",
252    feature = "all-parsers",
253    feature = "tsx-napi",
254    feature = "napi-compatible"
255))]
256static TSX_LANG: OnceLock<TSLanguage> = OnceLock::new();
257#[cfg(any(
258    feature = "typescript",
259    feature = "all-parsers",
260    feature = "typescript-napi",
261    feature = "napi-compatible"
262))]
263static TYPESCRIPT_LANG: OnceLock<TSLanguage> = OnceLock::new();
264#[cfg(any(feature = "yaml", feature = "all-parsers"))]
265static YAML_LANG: OnceLock<TSLanguage> = OnceLock::new();
266
267#[cfg(any(feature = "bash", feature = "all-parsers"))]
268pub fn language_bash() -> TSLanguage {
269    BASH_LANG
270        .get_or_init(|| into_lang!(tree_sitter_bash))
271        .clone()
272}
273#[cfg(any(feature = "c", feature = "all-parsers"))]
274pub fn language_c() -> TSLanguage {
275    C_LANG.get_or_init(|| into_lang!(tree_sitter_c)).clone()
276}
277#[cfg(any(feature = "cpp", feature = "all-parsers"))]
278pub fn language_cpp() -> TSLanguage {
279    CPP_LANG.get_or_init(|| into_lang!(tree_sitter_cpp)).clone()
280}
281#[cfg(any(feature = "csharp", feature = "all-parsers"))]
282pub fn language_c_sharp() -> TSLanguage {
283    CSHARP_LANG
284        .get_or_init(|| into_lang!(tree_sitter_c_sharp))
285        .clone()
286}
287#[cfg(any(
288    feature = "css",
289    feature = "all-parsers",
290    feature = "css-napi",
291    feature = "napi-compatible"
292))]
293pub fn language_css() -> TSLanguage {
294    CSS_LANG
295        .get_or_init(|| into_napi_lang!(tree_sitter_css::LANGUAGE))
296        .clone()
297}
298#[cfg(any(feature = "elixir", feature = "all-parsers"))]
299pub fn language_elixir() -> TSLanguage {
300    ELIXIR_LANG
301        .get_or_init(|| into_lang!(tree_sitter_elixir))
302        .clone()
303}
304#[cfg(any(feature = "go", feature = "all-parsers"))]
305pub fn language_go() -> TSLanguage {
306    GO_LANG.get_or_init(|| into_lang!(tree_sitter_go)).clone()
307}
308
309#[cfg(any(feature = "haskell", feature = "all-parsers"))]
310pub fn language_haskell() -> TSLanguage {
311    HASKELL_LANG
312        .get_or_init(|| into_lang!(tree_sitter_haskell))
313        .clone()
314}
315
316#[cfg(any(feature = "hcl", feature = "all-parsers"))]
317pub fn language_hcl() -> TSLanguage {
318    HCL_LANG.get_or_init(|| into_lang!(tree_sitter_hcl)).clone()
319}
320
321#[cfg(any(
322    feature = "html",
323    feature = "all-parsers",
324    feature = "html-napi",
325    feature = "napi-compatible"
326))]
327pub fn language_html() -> TSLanguage {
328    HTML_LANG
329        .get_or_init(|| into_napi_lang!(tree_sitter_html::LANGUAGE))
330        .clone()
331}
332
333#[cfg(any(feature = "java", feature = "all-parsers"))]
334pub fn language_java() -> TSLanguage {
335    JAVA_LANG
336        .get_or_init(|| into_lang!(tree_sitter_java))
337        .clone()
338}
339#[cfg(any(
340    feature = "javascript",
341    feature = "all-parsers",
342    feature = "javascript-napi",
343    feature = "napi-compatible"
344))]
345pub fn language_javascript() -> TSLanguage {
346    JAVASCRIPT_LANG
347        .get_or_init(|| into_napi_lang!(tree_sitter_javascript::LANGUAGE))
348        .clone()
349}
350#[cfg(any(feature = "json", feature = "all-parsers"))]
351pub fn language_json() -> TSLanguage {
352    JSON_LANG
353        .get_or_init(|| into_lang!(tree_sitter_json))
354        .clone()
355}
356#[cfg(any(feature = "kotlin", feature = "all-parsers"))]
357pub fn language_kotlin() -> TSLanguage {
358    KOTLIN_LANG
359        .get_or_init(|| into_lang!(tree_sitter_kotlin))
360        .clone()
361}
362
363#[cfg(any(feature = "lua", feature = "all-parsers"))]
364pub fn language_lua() -> TSLanguage {
365    LUA_LANG.get_or_init(|| into_lang!(tree_sitter_lua)).clone()
366}
367
368#[cfg(any(feature = "nix", feature = "all-parsers"))]
369pub fn language_nix() -> TSLanguage {
370    NIX_LANG.get_or_init(|| into_lang!(tree_sitter_nix)).clone()
371}
372
373#[cfg(any(feature = "php", feature = "all-parsers"))]
374pub fn language_php() -> TSLanguage {
375    PHP_LANG
376        .get_or_init(|| into_lang!(tree_sitter_php, LANGUAGE_PHP_ONLY))
377        .clone()
378}
379#[cfg(any(feature = "python", feature = "all-parsers"))]
380pub fn language_python() -> TSLanguage {
381    PYTHON_LANG
382        .get_or_init(|| into_lang!(tree_sitter_python))
383        .clone()
384}
385#[cfg(any(feature = "ruby", feature = "all-parsers"))]
386pub fn language_ruby() -> TSLanguage {
387    RUBY_LANG
388        .get_or_init(|| into_lang!(tree_sitter_ruby))
389        .clone()
390}
391#[cfg(any(feature = "rust", feature = "all-parsers"))]
392pub fn language_rust() -> TSLanguage {
393    RUST_LANG
394        .get_or_init(|| into_lang!(tree_sitter_rust))
395        .clone()
396}
397#[cfg(any(feature = "scala", feature = "all-parsers"))]
398pub fn language_scala() -> TSLanguage {
399    SCALA_LANG
400        .get_or_init(|| into_lang!(tree_sitter_scala))
401        .clone()
402}
403#[cfg(any(feature = "solidity", feature = "all-parsers"))]
404pub fn language_solidity() -> TSLanguage {
405    SOLIDITY_LANG
406        .get_or_init(|| into_lang!(tree_sitter_solidity))
407        .clone()
408}
409#[cfg(any(feature = "swift", feature = "all-parsers"))]
410pub fn language_swift() -> TSLanguage {
411    SWIFT_LANG
412        .get_or_init(|| into_lang!(tree_sitter_swift))
413        .clone()
414}
415#[cfg(any(
416    feature = "tsx",
417    feature = "all-parsers",
418    feature = "tsx-napi",
419    feature = "napi-compatible"
420))]
421pub fn language_tsx() -> TSLanguage {
422    TSX_LANG
423        .get_or_init(|| into_napi_lang!(tree_sitter_typescript::LANGUAGE_TSX))
424        .clone()
425}
426#[cfg(any(
427    feature = "typescript",
428    feature = "all-parsers",
429    feature = "typescript-napi",
430    feature = "napi-compatible"
431))]
432pub fn language_typescript() -> TSLanguage {
433    TYPESCRIPT_LANG
434        .get_or_init(|| into_napi_lang!(tree_sitter_typescript::LANGUAGE_TYPESCRIPT))
435        .clone()
436}
437#[cfg(any(feature = "yaml", feature = "all-parsers"))]
438pub fn language_yaml() -> TSLanguage {
439    YAML_LANG
440        .get_or_init(|| into_lang!(tree_sitter_yaml))
441        .clone()
442}