Skip to main content

thread_language/
lib.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//! Language definitions and tree-sitter parsers for Thread AST analysis.
8//!
9//! Provides unified language support through consistent [`Language`] and [`LanguageExt`] traits
10//! across 24+ programming languages. Each language can be feature-gated individually or included
11//! in groups.
12//!
13//! ## Language Categories
14//!
15//! ### Standard Languages
16//! Languages that accept `$` as a valid identifier character and use default pattern processing:
17//! - [`Bash`], [`Java`], [`JavaScript`], [`Json`], [`Lua`], [`Scala`], [`TypeScript`], [`Tsx`], [`Yaml`]
18//!
19//! ### Custom Pattern Languages
20//! Languages requiring special metavariable handling with custom expando characters:
21//! - [`C`] (`µ`), [`Cpp`] (`µ`), [`CSharp`] (`µ`), [`Css`] (`_`), [`Elixir`] (`µ`)
22//! - [`Go`] (`µ`), [`Haskell`] (`µ`), [`Hcl`] (`µ`), [`Html`] (`z`), [`Kotlin`] (`µ`), [`Nix`](`_`), [`Php`] (`µ`)
23//! - [`Python`] (`µ`), [`Ruby`] (`µ`), [`Rust`] (`µ`), [`Swift`] (`µ`)
24//!
25//! ## Usage
26//!
27//! ```rust
28//! use thread_language::{SupportLang, Rust};
29//! use thread_ast_engine::{Language, LanguageExt};
30//!
31//! // Runtime language selection
32//! let lang = SupportLang::from_path("main.rs").unwrap();
33//! let tree = lang.ast_grep("fn main() {}");
34//!
35//! // Compile-time language selection
36//! let rust = Rust;
37//! let tree = rust.ast_grep("fn main() {}");
38//! ```
39//!
40//! ## Implementation Details
41//!
42//! Languages are implemented using two macros:
43//! - [`impl_lang!`] - Standard languages accepting `$` in identifiers
44//! - [`impl_lang_expando!`] - Languages requiring custom expando characters for metavariables
45
46pub mod constants;
47pub mod ext_iden;
48#[cfg(any(
49    feature = "all-parsers",
50    feature = "napi-environment",
51    feature = "napi-compatible",
52    feature = "bash",
53    feature = "c",
54    feature = "cpp",
55    feature = "csharp",
56    feature = "css",
57    feature = "elixir",
58    feature = "go",
59    feature = "haskell",
60    feature = "hcl",
61    feature = "html",
62    feature = "java",
63    feature = "javascript",
64    feature = "json",
65    feature = "kotlin",
66    feature = "lua",
67    feature = "nix",
68    feature = "php",
69    feature = "python",
70    feature = "ruby",
71    feature = "rust",
72    feature = "scala",
73    feature = "solidity",
74    feature = "swift",
75    feature = "tsx",
76    feature = "typescript",
77    feature = "yaml"
78))]
79pub mod parsers;
80
81#[cfg(any(feature = "bash", feature = "all-parsers"))]
82mod bash;
83#[cfg(any(feature = "cpp", feature = "all-parsers"))]
84mod cpp;
85#[cfg(any(feature = "csharp", feature = "all-parsers"))]
86mod csharp;
87#[cfg(any(
88    feature = "css",
89    feature = "all-parsers",
90    feature = "css-napi",
91    feature = "napi-compatible"
92))]
93mod css;
94#[cfg(any(feature = "elixir", feature = "all-parsers"))]
95mod elixir;
96#[cfg(any(feature = "go", feature = "all-parsers"))]
97mod go;
98#[cfg(any(feature = "haskell", feature = "all-parsers"))]
99mod haskell;
100#[cfg(any(feature = "hcl", feature = "all-parsers"))]
101mod hcl;
102#[cfg(any(
103    feature = "html",
104    feature = "all-parsers",
105    feature = "html-napi",
106    feature = "napi-compatible"
107))]
108mod html;
109#[cfg(any(feature = "java", feature = "all-parsers"))]
110#[cfg(any(
111    feature = "javascript",
112    feature = "all-parsers",
113    feature = "javascript-napi",
114    feature = "napi-compatible"
115))]
116#[cfg(any(feature = "json", feature = "all-parsers"))]
117mod json;
118#[cfg(any(feature = "kotlin", feature = "all-parsers"))]
119mod kotlin;
120#[cfg(any(feature = "lua", feature = "all-parsers"))]
121mod lua;
122#[cfg(any(feature = "nix", feature = "all-parsers"))]
123mod nix;
124#[cfg(any(feature = "php", feature = "all-parsers"))]
125mod php;
126#[cfg(any(feature = "python", feature = "all-parsers"))]
127mod python;
128#[cfg(any(feature = "ruby", feature = "all-parsers"))]
129mod ruby;
130#[cfg(any(feature = "rust", feature = "all-parsers"))]
131mod rust;
132#[cfg(any(feature = "scala", feature = "all-parsers"))]
133mod scala;
134#[cfg(any(feature = "solidity", feature = "all-parsers"))]
135mod solidity;
136#[cfg(any(feature = "swift", feature = "all-parsers"))]
137mod swift;
138#[cfg(any(feature = "yaml", feature = "all-parsers"))]
139mod yaml;
140#[cfg(any(
141    feature = "html",
142    feature = "all-parsers",
143    feature = "html-napi",
144    feature = "napi-compatible"
145))]
146pub use html::Html;
147
148#[cfg(feature = "matching")]
149use thread_ast_engine::{Pattern, PatternBuilder, PatternError};
150#[cfg(feature = "profiling")]
151pub mod profiling;
152
153#[allow(unused_imports)]
154use ignore::types::{Types, TypesBuilder};
155#[allow(unused_imports)]
156use serde::de::Visitor;
157#[allow(unused_imports)]
158use serde::{Deserialize, Deserializer, Serialize, de};
159#[allow(unused_imports)]
160use std::borrow::Cow;
161use std::fmt::{self, Display, Formatter};
162use std::path::Path;
163use std::str::FromStr;
164
165#[allow(unused_imports)]
166use thread_ast_engine::Node;
167#[allow(unused_imports)]
168use thread_ast_engine::language::Language;
169#[allow(unused_imports)]
170#[cfg(feature = "matching")]
171use thread_ast_engine::meta_var::MetaVariable;
172#[allow(unused_imports)]
173#[cfg(feature = "tree-sitter-parsing")]
174use thread_ast_engine::tree_sitter::{LanguageExt, StrDoc, TSLanguage, TSRange};
175#[allow(unused_imports)]
176use thread_utilities::RapidMap;
177
178/// Implements standard [`Language`] and [`LanguageExt`] traits for languages that accept `$` in identifiers.
179///
180/// Used for languages like JavaScript, Python, and Rust where `$` can appear in variable names
181/// and doesn't require special preprocessing for metavariables.
182///
183/// # Parameters
184/// - `$lang` - The language struct name (e.g., `JavaScript`)
185/// - `$func` - The parser function name from [`parsers`] module (e.g., `language_javascript`)
186///
187/// # Generated Implementation
188/// Creates a zero-sized struct with [`Language`] and [`LanguageExt`] implementations that:
189/// - Map node kinds and field names to tree-sitter IDs
190/// - Build patterns using the language's parser
191/// - Use default metavariable processing (no expando character substitution)
192#[cfg(any(
193    feature = "all-parsers",
194    feature = "napi-compatible",
195    feature = "bash",
196    feature = "java",
197    feature = "javascript",
198    feature = "javascript-napi",
199    feature = "json",
200    feature = "lua",
201    feature = "solidity",
202    feature = "scala",
203    feature = "tsx",
204    feature = "tsx-napi",
205    feature = "typescript",
206    feature = "typescript-napi",
207    feature = "yaml",
208))]
209macro_rules! impl_lang {
210    ($lang: ident, $func: ident) => {
211        #[derive(Clone, Copy, Debug)]
212        pub struct $lang;
213        impl Language for $lang {
214            fn kind_to_id(&self, kind: &str) -> u16 {
215                self.get_ts_language()
216                    .id_for_node_kind(kind, /*named*/ true)
217            }
218            fn field_to_id(&self, field: &str) -> Option<u16> {
219                self.get_ts_language()
220                    .field_id_for_name(field)
221                    .map(|f| f.get())
222            }
223            #[cfg(feature = "matching")]
224            fn build_pattern(&self, builder: &PatternBuilder) -> Result<Pattern, PatternError> {
225                builder.build(|src| StrDoc::try_new(src, self.clone()))
226            }
227        }
228        impl LanguageExt for $lang {
229            fn get_ts_language(&self) -> TSLanguage {
230                parsers::$func().into()
231            }
232        }
233    };
234}
235
236/// Preprocesses pattern strings by replacing `$` with the language's expando character.
237///
238/// Languages that don't accept `$` in identifiers need metavariables like `$VAR` converted
239/// to use a different character. This function efficiently replaces `$` symbols that precede
240/// uppercase letters, underscores, or appear in triple sequences (`$$$`).
241///
242/// # Parameters
243/// - `expando` - The character to replace `$` with (e.g., `µ` for most languages, `_` for CSS)
244/// - `query` - The pattern string containing `$` metavariables
245///
246/// # Returns
247/// - `Cow::Borrowed` if no replacement is needed (fast path)
248/// - `Cow::Owned` if replacement occurred
249///
250/// # Examples
251/// ```rust
252/// # use thread_language::pre_process_pattern;
253/// // Python doesn't accept $ in identifiers, so use µ
254/// let result = pre_process_pattern('µ', "def $FUNC($ARG): pass");
255/// assert_eq!(result, "def µFUNC(µARG): pass");
256///
257/// // No change needed
258/// let result = pre_process_pattern('µ', "def hello(): pass");
259/// assert_eq!(result, "def hello(): pass");
260/// ```
261#[allow(dead_code)]
262fn pre_process_pattern(expando: char, query: &str) -> std::borrow::Cow<'_, str> {
263    // Fast path: check if any processing is needed
264    let has_dollar = query.as_bytes().contains(&b'$');
265    if !has_dollar {
266        return std::borrow::Cow::Borrowed(query);
267    }
268
269    // Count exact size needed to avoid reallocations
270    let mut size_needed = 0;
271    let mut needs_processing = false;
272    let mut dollar_count = 0;
273
274    for c in query.chars() {
275        if c == '$' {
276            dollar_count += 1;
277        } else {
278            let need_replace = matches!(c, 'A'..='Z' | '_') || dollar_count == 3;
279            if need_replace && dollar_count > 0 {
280                needs_processing = true;
281            }
282            size_needed += dollar_count + 1;
283            dollar_count = 0;
284        }
285    }
286    size_needed += dollar_count;
287
288    // If no replacement needed, return borrowed
289    if !needs_processing {
290        return std::borrow::Cow::Borrowed(query);
291    }
292
293    // Pre-allocate exact size and process in-place
294    let mut ret = String::with_capacity(size_needed);
295    dollar_count = 0;
296
297    for c in query.chars() {
298        if c == '$' {
299            dollar_count += 1;
300            continue;
301        }
302        let need_replace = matches!(c, 'A'..='Z' | '_') || dollar_count == 3;
303        let sigil = if need_replace { expando } else { '$' };
304
305        // Push dollars directly without iterator allocation
306        for _ in 0..dollar_count {
307            ret.push(sigil);
308        }
309        dollar_count = 0;
310        ret.push(c);
311    }
312
313    // Handle trailing dollars
314    let sigil = if dollar_count == 3 { expando } else { '$' };
315    for _ in 0..dollar_count {
316        ret.push(sigil);
317    }
318
319    std::borrow::Cow::Owned(ret)
320}
321
322/// Implements [`Language`] and [`LanguageExt`] traits for languages requiring custom expando characters.
323///
324/// Used for languages that don't accept `$` in identifiers and need metavariables like `$VAR`
325/// converted to use a different character (e.g., `µVAR`, `_VAR`).
326///
327/// # Parameters
328/// - `$lang` - The language struct name (e.g., `Python`)
329/// - `$func` - The parser function name from [`parsers`] module (e.g., `language_python`)
330/// - `$char` - The expando character to use instead of `$` (e.g., `'µ'`)
331///
332/// # Generated Implementation
333/// Creates a zero-sized struct with [`Language`] and [`LanguageExt`] implementations that:
334/// - Map node kinds and field names to tree-sitter IDs
335/// - Build patterns using the language's parser
336/// - Preprocess patterns by replacing `$` with the expando character
337/// - Provide the expando character via [`Language::expando_char`]
338///
339/// # Examples
340/// ```rust
341/// # use thread_language::Python;
342/// # use thread_ast_engine::Language;
343/// let python = Python;
344/// assert_eq!(python.expando_char(), 'µ');
345///
346/// // Pattern gets automatically preprocessed
347/// let pattern = "def $FUNC($ARG): pass";
348/// let processed = python.pre_process_pattern(pattern);
349/// assert_eq!(processed, "def µFUNC(µARG): pass");
350/// ```
351#[cfg(any(
352    feature = "all-parsers",
353    feature = "napi-compatible",
354    feature = "c",
355    feature = "cpp",
356    feature = "csharp",
357    feature = "css",
358    feature = "css-napi",
359    feature = "elixir",
360    feature = "go",
361    feature = "haskell",
362    feature = "hcl",
363    feature = "html",
364    feature = "html-napi",
365    feature = "kotlin",
366    feature = "nix",
367    feature = "php",
368    feature = "python",
369    feature = "ruby",
370    feature = "rust",
371))]
372macro_rules! impl_lang_expando {
373    ($lang: ident, $func: ident, $char: expr) => {
374        #[derive(Clone, Copy, Debug)]
375        pub struct $lang;
376        impl Language for $lang {
377            fn kind_to_id(&self, kind: &str) -> u16 {
378                self.get_ts_language()
379                    .id_for_node_kind(kind, /*named*/ true)
380            }
381            fn field_to_id(&self, field: &str) -> Option<u16> {
382                self.get_ts_language()
383                    .field_id_for_name(field)
384                    .map(|f| f.get())
385            }
386            fn expando_char(&self) -> char {
387                $char
388            }
389            fn pre_process_pattern<'q>(&self, query: &'q str) -> std::borrow::Cow<'q, str> {
390                pre_process_pattern(self.expando_char(), query)
391            }
392            #[cfg(feature = "matching")]
393            fn build_pattern(&self, builder: &PatternBuilder) -> Result<Pattern, PatternError> {
394                builder.build(|src| StrDoc::try_new(src, self.clone()))
395            }
396        }
397        impl LanguageExt for $lang {
398            fn get_ts_language(&self) -> TSLanguage {
399                $crate::parsers::$func().into()
400            }
401        }
402    };
403}
404
405pub trait Alias: Display {
406    const ALIAS: &'static [&'static str];
407}
408
409/// Implements the `ALIAS` associated constant for the given lang, which is
410/// then used to define the `alias` const fn and a `Deserialize` impl.
411#[cfg(all(
412    any(
413        feature = "all-parsers",
414        feature = "napi-compatible",
415        feature = "css-napi",
416        feature = "html-napi",
417        feature = "javascript-napi",
418        feature = "typescript-napi",
419        feature = "tsx-napi",
420        feature = "bash",
421        feature = "c",
422        feature = "cpp",
423        feature = "csharp",
424        feature = "css",
425        feature = "elixir",
426        feature = "go",
427        feature = "haskell",
428        feature = "hcl",
429        feature = "html",
430        feature = "java",
431        feature = "javascript",
432        feature = "json",
433        feature = "kotlin",
434        feature = "lua",
435        feature = "nix",
436        feature = "php",
437        feature = "python",
438        feature = "ruby",
439        feature = "rust",
440        feature = "scala",
441        feature = "solidity",
442        feature = "swift",
443        feature = "tsx",
444        feature = "typescript",
445        feature = "yaml"
446    ),
447    not(feature = "no-enabled-langs")
448))]
449macro_rules! impl_alias {
450    ($lang:ident => $as:expr) => {
451        impl Alias for $lang {
452            const ALIAS: &'static [&'static str] = $as;
453        }
454
455        impl fmt::Display for $lang {
456            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457                write!(f, "{:?}", self)
458            }
459        }
460
461        impl<'de> Deserialize<'de> for $lang {
462            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
463            where
464                D: Deserializer<'de>,
465            {
466                let vis = AliasVisitor {
467                    aliases: Self::ALIAS,
468                };
469                deserializer.deserialize_str(vis)?;
470                Ok($lang)
471            }
472        }
473
474        impl From<$lang> for SupportLang {
475            fn from(_: $lang) -> Self {
476                Self::$lang
477            }
478        }
479    };
480}
481/// Generates as convenience conversions between the lang types
482/// and `SupportedType`.
483#[cfg(all(
484    any(
485        feature = "all-parsers",
486        feature = "napi-compatible",
487        feature = "css-napi",
488        feature = "html-napi",
489        feature = "javascript-napi",
490        feature = "typescript-napi",
491        feature = "tsx-napi",
492        feature = "bash",
493        feature = "c",
494        feature = "cpp",
495        feature = "csharp",
496        feature = "css",
497        feature = "elixir",
498        feature = "go",
499        feature = "haskell",
500        feature = "hcl",
501        feature = "html",
502        feature = "java",
503        feature = "javascript",
504        feature = "json",
505        feature = "kotlin",
506        feature = "lua",
507        feature = "nix",
508        feature = "php",
509        feature = "python",
510        feature = "ruby",
511        feature = "rust",
512        feature = "scala",
513        feature = "solidity",
514        feature = "swift",
515        feature = "tsx",
516        feature = "typescript",
517        feature = "yaml"
518    ),
519    not(feature = "no-enabled-langs")
520))]
521macro_rules! impl_aliases {
522  ($($lang:ident, $feature:literal => $as:expr),* $(,)?) => {
523    $(#[cfg(feature = $feature)]
524      impl_alias!($lang => $as);
525    )*
526    #[allow(dead_code)]
527    const fn alias(lang: SupportLang) -> &'static [&'static str] {
528      match lang {
529        $(
530          #[cfg(feature = $feature)]
531          SupportLang::$lang => $lang::ALIAS,
532        )*
533      }
534    }
535  };
536}
537/* Customized Language with expando_char / pre_process_pattern */
538
539// https://en.cppreference.com/w/cpp/language/identifiers
540// Due to some issues in the tree-sitter parser, it is not possible to use
541// unicode literals in identifiers for C/C++ parsers
542#[cfg(any(feature = "c", feature = "all-parsers"))]
543impl_lang_expando!(C, language_c, 'µ');
544#[cfg(any(feature = "cpp", feature = "all-parsers"))]
545impl_lang_expando!(Cpp, language_cpp, 'µ');
546
547// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure#643-identifiers
548// all letter number is accepted
549// https://www.compart.com/en/unicode/category/Nl
550#[cfg(any(feature = "csharp", feature = "all-parsers"))]
551impl_lang_expando!(CSharp, language_c_sharp, 'µ');
552
553// https://www.w3.org/TR/CSS21/grammar.html#scanner
554#[cfg(any(
555    feature = "css",
556    feature = "all-parsers",
557    feature = "css-napi",
558    feature = "napi-compatible"
559))]
560impl_lang_expando!(Css, language_css, '_');
561
562// https://github.com/elixir-lang/tree-sitter-elixir/blob/a2861e88a730287a60c11ea9299c033c7d076e30/grammar.js#L245
563#[cfg(any(feature = "elixir", feature = "all-parsers"))]
564impl_lang_expando!(Elixir, language_elixir, 'µ');
565
566// we can use any Unicode code point categorized as "Letter"
567// https://go.dev/ref/spec#letter
568#[cfg(any(feature = "go", feature = "all-parsers"))]
569impl_lang_expando!(Go, language_go, 'µ');
570
571// GHC supports Unicode syntax per
572// https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/unicode_syntax.html
573// and the tree-sitter-haskell grammar parses it too.
574#[cfg(any(feature = "haskell", feature = "all-parsers"))]
575impl_lang_expando!(Haskell, language_haskell, 'µ');
576
577// https://developer.hashicorp.com/terraform/language/syntax/configuration#identifiers
578#[cfg(any(feature = "hcl", feature = "all-parsers"))]
579impl_lang_expando!(Hcl, language_hcl, 'µ');
580
581// https://github.com/fwcd/tree-sitter-kotlin/pull/93
582#[cfg(any(feature = "kotlin", feature = "all-parsers"))]
583impl_lang_expando!(Kotlin, language_kotlin, 'µ');
584
585// Nix uses $ for string interpolation (e.g., "${pkgs.hello}")
586#[cfg(any(feature = "nix", feature = "all-parsers"))]
587impl_lang_expando!(Nix, language_nix, '_');
588
589// PHP accepts unicode to be used as some name not var name though
590#[cfg(any(feature = "php", feature = "all-parsers"))]
591impl_lang_expando!(Php, language_php, 'µ');
592
593// we can use any char in unicode range [:XID_Start:]
594// https://docs.python.org/3/reference/lexical_analysis.html#identifiers
595// see also [PEP 3131](https://peps.python.org/pep-3131/) for further details.
596#[cfg(any(feature = "python", feature = "all-parsers"))]
597impl_lang_expando!(Python, language_python, 'µ');
598
599// https://github.com/tree-sitter/tree-sitter-ruby/blob/f257f3f57833d584050336921773738a3fd8ca22/grammar.js#L30C26-L30C78
600#[cfg(any(feature = "ruby", feature = "all-parsers"))]
601impl_lang_expando!(Ruby, language_ruby, 'µ');
602
603// we can use any char in unicode range [:XID_Start:]
604// https://doc.rust-lang.org/reference/identifiers.html
605#[cfg(any(feature = "rust", feature = "all-parsers"))]
606impl_lang_expando!(Rust, language_rust, 'µ');
607
608//https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/#Identifiers
609#[cfg(any(feature = "swift", feature = "all-parsers"))]
610impl_lang_expando!(Swift, language_swift, 'µ');
611
612// Stub Language without preprocessing
613// Language Name, tree-sitter-name, alias, extension
614#[cfg(any(feature = "bash", feature = "all-parsers"))]
615impl_lang!(Bash, language_bash);
616#[cfg(any(feature = "java", feature = "all-parsers"))]
617impl_lang!(Java, language_java);
618#[cfg(any(
619    feature = "javascript",
620    feature = "all-parsers",
621    feature = "javascript-napi",
622    feature = "napi-compatible"
623))]
624impl_lang!(JavaScript, language_javascript);
625#[cfg(any(feature = "json", feature = "all-parsers"))]
626impl_lang!(Json, language_json);
627#[cfg(any(feature = "lua", feature = "all-parsers"))]
628impl_lang!(Lua, language_lua);
629#[cfg(any(feature = "solidity", feature = "all-parsers"))]
630impl_lang!(Solidity, language_solidity);
631#[cfg(any(feature = "scala", feature = "all-parsers"))]
632impl_lang!(Scala, language_scala);
633#[cfg(any(
634    feature = "tsx",
635    feature = "all-parsers",
636    feature = "tsx-napi",
637    feature = "napi-compatible"
638))]
639impl_lang!(Tsx, language_tsx);
640#[cfg(any(
641    feature = "typescript",
642    feature = "all-parsers",
643    feature = "typescript-napi",
644    feature = "napi-compatible"
645))]
646impl_lang!(TypeScript, language_typescript);
647#[cfg(any(feature = "yaml", feature = "all-parsers"))]
648impl_lang!(Yaml, language_yaml);
649
650// See ripgrep for extensions
651// https://github.com/BurntSushi/ripgrep/blob/master/crates/ignore/src/default_types.rs
652
653/// Runtime language selection enum supporting all built-in languages.
654///
655/// Provides a unified interface for working with any supported language at runtime.
656/// Each variant corresponds to a specific programming language implementation.
657///
658/// # Language Detection
659/// ```rust,ignore
660/// use thread_language::SupportLang;
661/// use std::path::Path;
662///
663/// // Detect from file extension
664/// let lang = SupportLang::from_path("main.rs").unwrap();
665/// assert_eq!(lang, SupportLang::Rust);
666///
667/// // Parse from string
668/// let lang: SupportLang = "javascript".parse().unwrap();
669/// assert_eq!(lang, SupportLang::JavaScript);
670/// ```
671///
672/// # Usage with AST Analysis
673/// ```rust,ignore
674/// use thread_language::SupportLang;
675/// use thread_ast_engine::{Language, LanguageExt};
676///
677/// let lang = SupportLang::Rust;
678/// let tree = lang.ast_grep("fn main() {}");
679/// let pattern = lang.build_pattern(&pattern_builder).unwrap();
680/// ```
681#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Hash)]
682pub enum SupportLang {
683    #[cfg(any(feature = "bash", feature = "all-parsers"))]
684    Bash,
685    #[cfg(any(feature = "c", feature = "all-parsers"))]
686    C,
687    #[cfg(any(feature = "cpp", feature = "all-parsers"))]
688    Cpp,
689    #[cfg(any(feature = "csharp", feature = "all-parsers"))]
690    CSharp,
691    #[cfg(any(
692        feature = "css",
693        feature = "all-parsers",
694        feature = "css-napi",
695        feature = "napi-compatible"
696    ))]
697    Css,
698    #[cfg(any(feature = "elixir", feature = "all-parsers"))]
699    Elixir,
700    #[cfg(any(feature = "go", feature = "all-parsers"))]
701    Go,
702    #[cfg(any(feature = "haskell", feature = "all-parsers"))]
703    Haskell,
704    #[cfg(any(feature = "hcl", feature = "all-parsers"))]
705    Hcl,
706    #[cfg(any(
707        feature = "html",
708        feature = "all-parsers",
709        feature = "html-napi",
710        feature = "napi-compatible"
711    ))]
712    Html,
713    #[cfg(any(feature = "java", feature = "all-parsers"))]
714    Java,
715    #[cfg(any(
716        feature = "javascript",
717        feature = "all-parsers",
718        feature = "javascript-napi",
719        feature = "napi-compatible"
720    ))]
721    JavaScript,
722    #[cfg(any(feature = "json", feature = "all-parsers"))]
723    Json,
724    #[cfg(any(feature = "kotlin", feature = "all-parsers"))]
725    Kotlin,
726    #[cfg(any(feature = "lua", feature = "all-parsers"))]
727    Lua,
728    #[cfg(any(feature = "nix", feature = "all-parsers"))]
729    Nix,
730    #[cfg(any(feature = "php", feature = "all-parsers"))]
731    Php,
732    #[cfg(any(feature = "python", feature = "all-parsers"))]
733    Python,
734    #[cfg(any(feature = "ruby", feature = "all-parsers"))]
735    Ruby,
736    #[cfg(any(feature = "rust", feature = "all-parsers"))]
737    Rust,
738    #[cfg(any(feature = "scala", feature = "all-parsers"))]
739    Scala,
740    #[cfg(any(feature = "solidity", feature = "all-parsers"))]
741    Solidity,
742    #[cfg(any(feature = "swift", feature = "all-parsers"))]
743    Swift,
744    #[cfg(any(
745        feature = "tsx",
746        feature = "all-parsers",
747        feature = "tsx-napi",
748        feature = "napi-compatible"
749    ))]
750    Tsx,
751    #[cfg(any(
752        feature = "typescript",
753        feature = "all-parsers",
754        feature = "typescript-napi",
755        feature = "napi-compatible"
756    ))]
757    TypeScript,
758    #[cfg(any(feature = "yaml", feature = "all-parsers"))]
759    Yaml,
760    #[cfg(not(any(
761        feature = "all-parsers",
762        feature = "napi-compatible",
763        feature = "css-napi",
764        feature = "html-napi",
765        feature = "javascript-napi",
766        feature = "typescript-napi",
767        feature = "tsx-napi",
768        feature = "bash",
769        feature = "c",
770        feature = "cpp",
771        feature = "csharp",
772        feature = "css",
773        feature = "elixir",
774        feature = "go",
775        feature = "haskell",
776        feature = "hcl",
777        feature = "html",
778        feature = "java",
779        feature = "javascript",
780        feature = "json",
781        feature = "kotlin",
782        feature = "nix",
783        feature = "lua",
784        feature = "php",
785        feature = "python",
786        feature = "ruby",
787        feature = "rust",
788        feature = "scala",
789        feature = "solidity",
790        feature = "swift",
791        feature = "tsx",
792        feature = "typescript",
793        feature = "yaml"
794    )))]
795    NoEnabledLangs,
796}
797
798impl SupportLang {
799    pub const fn all_langs() -> &'static [SupportLang] {
800        use SupportLang::*;
801        &[
802            #[cfg(any(feature = "bash", feature = "all-parsers"))]
803            Bash,
804            #[cfg(any(feature = "c", feature = "all-parsers"))]
805            C,
806            #[cfg(any(feature = "cpp", feature = "all-parsers"))]
807            Cpp,
808            #[cfg(any(feature = "csharp", feature = "all-parsers"))]
809            CSharp,
810            #[cfg(any(
811                feature = "css",
812                feature = "all-parsers",
813                feature = "css-napi",
814                feature = "napi-compatible"
815            ))]
816            Css,
817            #[cfg(any(feature = "elixir", feature = "all-parsers"))]
818            Elixir,
819            #[cfg(any(feature = "go", feature = "all-parsers"))]
820            Go,
821            #[cfg(any(feature = "haskell", feature = "all-parsers"))]
822            Haskell,
823            #[cfg(any(feature = "hcl", feature = "all-parsers"))]
824            Hcl,
825            #[cfg(any(
826                feature = "html",
827                feature = "all-parsers",
828                feature = "html-napi",
829                feature = "napi-compatible"
830            ))]
831            Html,
832            #[cfg(any(feature = "java", feature = "all-parsers"))]
833            Java,
834            #[cfg(any(
835                feature = "javascript",
836                feature = "all-parsers",
837                feature = "javascript-napi",
838                feature = "napi-compatible"
839            ))]
840            JavaScript,
841            #[cfg(any(feature = "json", feature = "all-parsers"))]
842            Json,
843            #[cfg(any(feature = "kotlin", feature = "all-parsers"))]
844            Kotlin,
845            #[cfg(any(feature = "lua", feature = "all-parsers"))]
846            Lua,
847            #[cfg(any(feature = "nix", feature = "all-parsers"))]
848            Nix,
849            #[cfg(any(feature = "php", feature = "all-parsers"))]
850            Php,
851            #[cfg(any(feature = "python", feature = "all-parsers"))]
852            Python,
853            #[cfg(any(feature = "ruby", feature = "all-parsers"))]
854            Ruby,
855            #[cfg(any(feature = "rust", feature = "all-parsers"))]
856            Rust,
857            #[cfg(any(feature = "scala", feature = "all-parsers"))]
858            Scala,
859            #[cfg(any(feature = "solidity", feature = "all-parsers"))]
860            Solidity,
861            #[cfg(any(feature = "swift", feature = "all-parsers"))]
862            Swift,
863            #[cfg(any(
864                feature = "tsx",
865                feature = "all-parsers",
866                feature = "tsx-napi",
867                feature = "napi-compatible"
868            ))]
869            Tsx,
870            #[cfg(any(
871                feature = "typescript",
872                feature = "all-parsers",
873                feature = "typescript-napi",
874                feature = "napi-compatible"
875            ))]
876            TypeScript,
877            #[cfg(any(feature = "yaml", feature = "all-parsers"))]
878            Yaml,
879            #[cfg(not(any(
880                feature = "all-parsers",
881                feature = "napi-compatible",
882                feature = "css-napi",
883                feature = "html-napi",
884                feature = "javascript-napi",
885                feature = "typescript-napi",
886                feature = "tsx-napi",
887                feature = "bash",
888                feature = "c",
889                feature = "cpp",
890                feature = "csharp",
891                feature = "css",
892                feature = "elixir",
893                feature = "go",
894                feature = "haskell",
895                feature = "hcl",
896                feature = "html",
897                feature = "java",
898                feature = "javascript",
899                feature = "json",
900                feature = "kotlin",
901                feature = "lua",
902                feature = "nix",
903                feature = "php",
904                feature = "python",
905                feature = "ruby",
906                feature = "rust",
907                feature = "scala",
908                feature = "solidity",
909                feature = "swift",
910                feature = "tsx",
911                feature = "typescript",
912                feature = "yaml"
913            )))]
914            NoEnabledLangs,
915        ]
916    }
917
918    pub fn file_types(&self) -> Types {
919        file_types(*self)
920    }
921}
922
923impl fmt::Display for SupportLang {
924    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
925        write!(f, "{self:?}")
926    }
927}
928
929#[derive(Debug)]
930pub enum SupportLangErr {
931    LanguageNotSupported(String),
932    LanguageNotEnabled(String),
933}
934
935impl Display for SupportLangErr {
936    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
937        use SupportLangErr::*;
938        match self {
939            LanguageNotSupported(lang) => write!(f, "{lang} is not supported!"),
940            LanguageNotEnabled(lang) => write!(
941                f,
942                "{lang} is available but not enabled. You need to enable the feature flag for this language."
943            ),
944        }
945    }
946}
947
948impl std::error::Error for SupportLangErr {}
949
950#[cfg(any(
951    feature = "all-parsers",
952    feature = "napi-compatible",
953    feature = "css-napi",
954    feature = "html-napi",
955    feature = "javascript-napi",
956    feature = "typescript-napi",
957    feature = "tsx-napi",
958    feature = "bash",
959    feature = "c",
960    feature = "cpp",
961    feature = "csharp",
962    feature = "css",
963    feature = "elixir",
964    feature = "go",
965    feature = "haskell",
966    feature = "hcl",
967    feature = "html",
968    feature = "java",
969    feature = "javascript",
970    feature = "json",
971    feature = "kotlin",
972    feature = "lua",
973    feature = "nix",
974    feature = "php",
975    feature = "python",
976    feature = "ruby",
977    feature = "rust",
978    feature = "scala",
979    feature = "solidity",
980    feature = "swift",
981    feature = "tsx",
982    feature = "typescript",
983    feature = "yaml"
984))]
985impl<'de> Deserialize<'de> for SupportLang {
986    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
987    where
988        D: Deserializer<'de>,
989    {
990        deserializer.deserialize_str(SupportLangVisitor)
991    }
992}
993#[allow(dead_code)]
994struct SupportLangVisitor;
995
996#[cfg(any(
997    feature = "all-parsers",
998    feature = "napi-compatible",
999    feature = "css-napi",
1000    feature = "html-napi",
1001    feature = "javascript-napi",
1002    feature = "typescript-napi",
1003    feature = "tsx-napi",
1004    feature = "bash",
1005    feature = "c",
1006    feature = "cpp",
1007    feature = "csharp",
1008    feature = "css",
1009    feature = "elixir",
1010    feature = "go",
1011    feature = "haskell",
1012    feature = "hcl",
1013    feature = "html",
1014    feature = "java",
1015    feature = "javascript",
1016    feature = "json",
1017    feature = "kotlin",
1018    feature = "lua",
1019    feature = "nix",
1020    feature = "php",
1021    feature = "python",
1022    feature = "ruby",
1023    feature = "rust",
1024    feature = "scala",
1025    feature = "solidity",
1026    feature = "swift",
1027    feature = "tsx",
1028    feature = "typescript",
1029    feature = "yaml"
1030))]
1031impl Visitor<'_> for SupportLangVisitor {
1032    type Value = SupportLang;
1033
1034    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
1035        f.write_str("SupportLang")
1036    }
1037
1038    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1039    where
1040        E: de::Error,
1041    {
1042        v.parse().map_err(de::Error::custom)
1043    }
1044}
1045
1046#[allow(dead_code)]
1047struct AliasVisitor {
1048    aliases: &'static [&'static str],
1049}
1050#[cfg(any(
1051    feature = "all-parsers",
1052    feature = "napi-compatible",
1053    feature = "css-napi",
1054    feature = "html-napi",
1055    feature = "javascript-napi",
1056    feature = "typescript-napi",
1057    feature = "tsx-napi",
1058    feature = "bash",
1059    feature = "c",
1060    feature = "cpp",
1061    feature = "csharp",
1062    feature = "css",
1063    feature = "elixir",
1064    feature = "go",
1065    feature = "haskell",
1066    feature = "hcl",
1067    feature = "html",
1068    feature = "java",
1069    feature = "javascript",
1070    feature = "json",
1071    feature = "kotlin",
1072    feature = "lua",
1073    feature = "nix",
1074    feature = "php",
1075    feature = "python",
1076    feature = "ruby",
1077    feature = "rust",
1078    feature = "scala",
1079    feature = "solidity",
1080    feature = "swift",
1081    feature = "tsx",
1082    feature = "typescript",
1083    feature = "yaml"
1084))]
1085impl Visitor<'_> for AliasVisitor {
1086    type Value = &'static str;
1087
1088    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
1089        write!(f, "one of {:?}", self.aliases)
1090    }
1091
1092    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1093    where
1094        E: de::Error,
1095    {
1096        self.aliases
1097            .iter()
1098            .copied()
1099            .find(|&a| v.eq_ignore_ascii_case(a))
1100            .ok_or_else(|| de::Error::invalid_value(de::Unexpected::Str(v), &self))
1101    }
1102}
1103#[cfg(all(
1104    any(
1105        feature = "all-parsers",
1106        feature = "napi-compatible",
1107        feature = "css-napi",
1108        feature = "html-napi",
1109        feature = "javascript-napi",
1110        feature = "typescript-napi",
1111        feature = "tsx-napi",
1112        feature = "bash",
1113        feature = "c",
1114        feature = "cpp",
1115        feature = "csharp",
1116        feature = "css",
1117        feature = "elixir",
1118        feature = "go",
1119        feature = "haskell",
1120        feature = "hcl",
1121        feature = "html",
1122        feature = "java",
1123        feature = "javascript",
1124        feature = "json",
1125        feature = "kotlin",
1126        feature = "lua",
1127        feature = "nix",
1128        feature = "php",
1129        feature = "python",
1130        feature = "ruby",
1131        feature = "rust",
1132        feature = "scala",
1133        feature = "solidity",
1134        feature = "swift",
1135        feature = "tsx",
1136        feature = "typescript",
1137        feature = "yaml"
1138    ),
1139    not(feature = "no-enabled-langs")
1140))]
1141impl_aliases! {
1142  Bash, "bash" => &["bash"],
1143  C, "c" => &["c"],
1144  Cpp, "cpp" => &["cc", "c++", "cpp", "cxx"],
1145  CSharp, "csharp" => &["cs", "csharp"],
1146  Css, "css" => &["css"],
1147  Elixir, "elixir" => &["ex", "elixir"],
1148  Go, "go" => &["go", "golang"],
1149  Haskell, "haskell" => &["hs", "haskell"],
1150  Hcl, "hcl" => &["hcl", "terraform"],
1151  Html, "html" => &["html"],
1152  Java, "java" => &["java"],
1153  JavaScript, "javascript" => &["javascript", "js", "jsx"],
1154  Json, "json" => &["json"],
1155  Kotlin, "kotlin" => &["kotlin", "kt"],
1156  Lua, "lua" => &["lua"],
1157  Nix, "nix" => &["nix"],
1158  Php, "php" => &["php"],
1159  Python, "python" => &["py", "python"],
1160  Ruby, "ruby" => &["rb", "ruby"],
1161  Rust, "rust" => &["rs", "rust"],
1162  Scala, "scala" => &["scala"],
1163  Solidity, "solidity" => &["sol", "solidity"],
1164  Swift, "swift" => &["swift"],
1165  TypeScript, "typescript" => &["ts", "typescript"],
1166  Tsx, "tsx" => &["tsx"],
1167  Yaml, "yaml" => &["yaml", "yml"],
1168  NoEnabledLangs, "no-enabled-langs" => &["no-enabled-langs"],
1169}
1170
1171/// Implements the language names and aliases.
1172impl FromStr for SupportLang {
1173    type Err = SupportLangErr;
1174    fn from_str(s: &str) -> Result<Self, Self::Err> {
1175        let mut str_matcher = s.trim().to_string();
1176        str_matcher.make_ascii_lowercase();
1177        match str_matcher.as_str() {
1178            #[cfg(any(feature = "bash", feature = "all-parsers"))]
1179            "bash" => Ok(SupportLang::Bash),
1180            #[cfg(any(feature = "c", feature = "all-parsers"))]
1181            "c" => Ok(SupportLang::C),
1182            #[cfg(any(feature = "cpp", feature = "all-parsers"))]
1183            "cpp" | "c++" => Ok(SupportLang::Cpp),
1184            #[cfg(any(feature = "csharp", feature = "all-parsers"))]
1185            "cs" | "csharp" => Ok(SupportLang::CSharp),
1186            #[cfg(any(
1187                feature = "css",
1188                feature = "all-parsers",
1189                feature = "css-napi",
1190                feature = "napi-compatible"
1191            ))]
1192            "css" => Ok(SupportLang::Css),
1193            #[cfg(any(feature = "elixir", feature = "all-parsers"))]
1194            "elixir" | "ex" => Ok(SupportLang::Elixir),
1195            #[cfg(any(feature = "go", feature = "all-parsers"))]
1196            "go" | "golang" => Ok(SupportLang::Go),
1197            #[cfg(any(feature = "haskell", feature = "all-parsers"))]
1198            "haskell" | "hs" => Ok(SupportLang::Haskell),
1199            #[cfg(any(feature = "hcl", feature = "all-parsers"))]
1200            "hcl" | "terraform" => Ok(SupportLang::Hcl),
1201            #[cfg(any(
1202                feature = "html",
1203                feature = "all-parsers",
1204                feature = "html-napi",
1205                feature = "napi-compatible"
1206            ))]
1207            "html" => Ok(SupportLang::Html),
1208            #[cfg(any(feature = "java", feature = "all-parsers"))]
1209            "java" => Ok(SupportLang::Java),
1210            #[cfg(any(
1211                feature = "javascript",
1212                feature = "all-parsers",
1213                feature = "javascript-napi",
1214                feature = "napi-compatible"
1215            ))]
1216            "javascript" | "js" => Ok(SupportLang::JavaScript),
1217            #[cfg(any(feature = "json", feature = "all-parsers"))]
1218            "json" => Ok(SupportLang::Json),
1219            #[cfg(any(feature = "kotlin", feature = "all-parsers"))]
1220            "kotlin" | "kt" => Ok(SupportLang::Kotlin),
1221            #[cfg(any(feature = "lua", feature = "all-parsers"))]
1222            "lua" => Ok(SupportLang::Lua),
1223            #[cfg(any(feature = "nix", feature = "all-parsers"))]
1224            "nix" => Ok(SupportLang::Nix),
1225            #[cfg(any(feature = "php", feature = "all-parsers"))]
1226            "php" => Ok(SupportLang::Php),
1227            #[cfg(any(feature = "python", feature = "all-parsers"))]
1228            "python" | "py" => Ok(SupportLang::Python),
1229            #[cfg(any(feature = "ruby", feature = "all-parsers"))]
1230            "ruby" | "rb" => Ok(SupportLang::Ruby),
1231            #[cfg(any(feature = "rust", feature = "all-parsers"))]
1232            "rust" | "rs" => Ok(SupportLang::Rust),
1233            #[cfg(any(feature = "scala", feature = "all-parsers"))]
1234            "scala" => Ok(SupportLang::Scala),
1235            #[cfg(any(feature = "solidity", feature = "all-parsers"))]
1236            "solidity" | "sol" => Ok(SupportLang::Solidity),
1237            #[cfg(any(feature = "swift", feature = "all-parsers"))]
1238            "swift" => Ok(SupportLang::Swift),
1239            #[cfg(any(
1240                feature = "typescript",
1241                feature = "all-parsers",
1242                feature = "typescript-napi",
1243                feature = "napi-compatible"
1244            ))]
1245            "typescript" | "ts" => Ok(SupportLang::TypeScript),
1246            #[cfg(any(
1247                feature = "tsx",
1248                feature = "all-parsers",
1249                feature = "tsx-napi",
1250                feature = "napi-compatible"
1251            ))]
1252            "tsx" => Ok(SupportLang::Tsx),
1253            #[cfg(any(feature = "yaml", feature = "all-parsers"))]
1254            "yaml" | "yml" => Ok(SupportLang::Yaml),
1255            #[cfg(not(any(
1256                feature = "all-parsers",
1257                feature = "napi-compatible",
1258                feature = "css-napi",
1259                feature = "html-napi",
1260                feature = "javascript-napi",
1261                feature = "typescript-napi",
1262                feature = "tsx-napi",
1263                feature = "bash",
1264                feature = "c",
1265                feature = "cpp",
1266                feature = "csharp",
1267                feature = "css",
1268                feature = "elixir",
1269                feature = "go",
1270                feature = "haskell",
1271                feature = "hcl",
1272                feature = "html",
1273                feature = "java",
1274                feature = "javascript",
1275                feature = "json",
1276                feature = "kotlin",
1277                feature = "lua",
1278                feature = "nix",
1279                feature = "php",
1280                feature = "python",
1281                feature = "ruby",
1282                feature = "rust",
1283                feature = "scala",
1284                feature = "solidity",
1285                feature = "swift",
1286                feature = "tsx",
1287                feature = "typescript",
1288                feature = "yaml"
1289            )))]
1290            "no-enabled-langs" => Ok(SupportLang::NoEnabledLangs),
1291
1292            _ => {
1293                if constants::ALL_SUPPORTED_LANGS.contains(&str_matcher.as_str()) {
1294                    Err(SupportLangErr::LanguageNotEnabled(format!(
1295                        "language {} was detected, but it is not enabled by feature flags. If you want to parse this kind of file, enable the flag in `thread-language`",
1296                        &str_matcher
1297                    )))
1298                } else {
1299                    Err(SupportLangErr::LanguageNotSupported(format!(
1300                        "language {} is not supported",
1301                        &str_matcher
1302                    )))
1303                }
1304            }
1305        }
1306    }
1307}
1308#[cfg(any(
1309    feature = "all-parsers",
1310    feature = "napi-compatible",
1311    feature = "css-napi",
1312    feature = "html-napi",
1313    feature = "javascript-napi",
1314    feature = "typescript-napi",
1315    feature = "tsx-napi",
1316    feature = "bash",
1317    feature = "c",
1318    feature = "cpp",
1319    feature = "csharp",
1320    feature = "css",
1321    feature = "elixir",
1322    feature = "go",
1323    feature = "haskell",
1324    feature = "hcl",
1325    feature = "html",
1326    feature = "java",
1327    feature = "javascript",
1328    feature = "json",
1329    feature = "kotlin",
1330    feature = "lua",
1331    feature = "nix",
1332    feature = "php",
1333    feature = "python",
1334    feature = "ruby",
1335    feature = "rust",
1336    feature = "scala",
1337    feature = "solidity",
1338    feature = "swift",
1339    feature = "tsx",
1340    feature = "typescript",
1341    feature = "yaml"
1342))]
1343macro_rules! execute_lang_method {
1344  ($me: path, $method: ident, $($pname:tt),*) => {
1345    use SupportLang as S;
1346    match $me {
1347        #[cfg(any(feature = "bash", feature = "all-parsers"))]
1348        S::Bash => Bash.$method($($pname,)*),
1349        #[cfg(any(feature = "c", feature = "all-parsers"))]
1350        S::C => C.$method($($pname,)*),
1351        #[cfg(any(feature = "cpp", feature = "all-parsers"))]
1352        S::Cpp => Cpp.$method($($pname,)*),
1353        #[cfg(any(feature = "csharp", feature = "all-parsers"))]
1354        S::CSharp => CSharp.$method($($pname,)*),
1355        #[cfg(any(feature = "css", feature = "all-parsers", feature = "css-napi", feature = "napi-compatible"))]
1356        S::Css => Css.$method($($pname,)*),
1357        #[cfg(any(feature = "elixir", feature = "all-parsers"))]
1358        S::Elixir => Elixir.$method($($pname,)*),
1359        #[cfg(any(feature = "go", feature = "all-parsers"))]
1360        S::Go => Go.$method($($pname,)*),
1361        #[cfg(any(feature = "haskell", feature = "all-parsers"))]
1362        S::Haskell => Haskell.$method($($pname,)*),
1363        #[cfg(any(feature = "hcl", feature = "all-parsers"))]
1364        S::Hcl => Hcl.$method($($pname,)*),
1365        #[cfg(any(feature = "html", feature = "all-parsers", feature = "html-napi", feature = "napi-compatible"))]
1366        S::Html => Html.$method($($pname,)*),
1367        #[cfg(any(feature = "java", feature = "all-parsers"))]
1368        S::Java => Java.$method($($pname,)*),
1369        #[cfg(any(feature = "javascript", feature = "all-parsers", feature = "javascript-napi", feature = "napi-compatible"))]
1370        S::JavaScript => JavaScript.$method($($pname,)*),
1371        #[cfg(any(feature = "json", feature = "all-parsers"))]
1372        S::Json => Json.$method($($pname,)*),
1373        #[cfg(any(feature = "kotlin", feature = "all-parsers"))]
1374        S::Kotlin => Kotlin.$method($($pname,)*),
1375        #[cfg(any(feature = "lua", feature = "all-parsers"))]
1376        S::Lua => Lua.$method($($pname,)*),
1377        #[cfg(any(feature = "nix", feature = "all-parsers"))]
1378        S::Nix => Nix.$method($($pname,)*),
1379        #[cfg(any(feature = "php", feature = "all-parsers"))]
1380        S::Php => Php.$method($($pname,)*),
1381        #[cfg(any(feature = "python", feature = "all-parsers"))]
1382        S::Python => Python.$method($($pname,)*),
1383        #[cfg(any(feature = "ruby", feature = "all-parsers"))]
1384        S::Ruby => Ruby.$method($($pname,)*),
1385        #[cfg(any(feature = "rust", feature = "all-parsers"))]
1386        S::Rust => Rust.$method($($pname,)*),
1387        #[cfg(any(feature = "scala", feature = "all-parsers"))]
1388        S::Scala => Scala.$method($($pname,)*),
1389        #[cfg(any(feature = "solidity", feature = "all-parsers"))]
1390        S::Solidity => Solidity.$method($($pname,)*),
1391        #[cfg(any(feature = "swift", feature = "all-parsers"))]
1392        S::Swift => Swift.$method($($pname,)*),
1393        #[cfg(any(feature = "tsx", feature = "all-parsers", feature = "tsx-napi", feature = "napi-compatible"))]
1394        S::Tsx => Tsx.$method($($pname,)*),
1395        #[cfg(any(feature = "typescript", feature = "all-parsers", feature = "typescript-napi", feature = "napi-compatible"))]
1396        S::TypeScript => TypeScript.$method($($pname,)*),
1397        #[cfg(any(feature = "yaml", feature = "all-parsers"))]
1398        S::Yaml => Yaml.$method($($pname,)*),
1399        #[cfg(not(any(
1400            feature = "all-parsers",
1401            feature = "napi-compatible",
1402            feature = "css-napi",
1403            feature = "html-napi",
1404            feature = "javascript-napi",
1405            feature = "typescript-napi",
1406            feature = "tsx-napi",
1407            feature = "bash",
1408            feature = "c",
1409            feature = "cpp",
1410            feature = "csharp",
1411            feature = "css",
1412            feature = "elixir",
1413            feature = "go",
1414            feature = "haskell",
1415            feature = "hcl",
1416            feature = "html",
1417            feature = "java",
1418            feature = "javascript",
1419            feature = "json",
1420            feature = "kotlin",
1421            feature = "lua",
1422            feature = "nix",
1423            feature = "php",
1424            feature = "python",
1425            feature = "ruby",
1426            feature = "rust",
1427            feature = "scala",
1428            feature = "solidity",
1429            feature = "swift",
1430            feature = "tsx",
1431            feature = "typescript",
1432            feature = "yaml"
1433        )))]
1434        S::NoEnabledLangs => {
1435            return Err(SupportLangErr::LanguageNotEnabled(
1436                "no-enabled-langs".to_string(),
1437            ))
1438        }
1439    }
1440  }
1441}
1442#[cfg(any(
1443    feature = "all-parsers",
1444    feature = "napi-compatible",
1445    feature = "css-napi",
1446    feature = "javascript-napi",
1447    feature = "html-napi",
1448    feature = "typescript-napi",
1449    feature = "tsx-napi",
1450    feature = "bash",
1451    feature = "c",
1452    feature = "cpp",
1453    feature = "csharp",
1454    feature = "css",
1455    feature = "elixir",
1456    feature = "go",
1457    feature = "haskell",
1458    feature = "hcl",
1459    feature = "html",
1460    feature = "java",
1461    feature = "javascript",
1462    feature = "json",
1463    feature = "kotlin",
1464    feature = "lua",
1465    feature = "nix",
1466    feature = "php",
1467    feature = "python",
1468    feature = "ruby",
1469    feature = "rust",
1470    feature = "scala",
1471    feature = "solidity",
1472    feature = "swift",
1473    feature = "tsx",
1474    feature = "typescript",
1475    feature = "yaml"
1476))]
1477macro_rules! impl_lang_method {
1478  ($method: ident, ($($pname:tt: $ptype:ty),*) => $return_type: ty) => {
1479    #[inline]
1480    fn $method(&self, $($pname: $ptype),*) -> $return_type {
1481      execute_lang_method!{ self, $method, $($pname),* }
1482    }
1483  };
1484}
1485#[cfg(all(
1486    feature = "matching",
1487    any(
1488        feature = "all-parsers",
1489        feature = "napi-environment",
1490        feature = "napi-compatible",
1491        feature = "bash",
1492        feature = "c",
1493        feature = "cpp",
1494        feature = "csharp",
1495        feature = "css",
1496        feature = "elixir",
1497        feature = "go",
1498        feature = "haskell",
1499        feature = "hcl",
1500        feature = "html",
1501        feature = "java",
1502        feature = "javascript",
1503        feature = "json",
1504        feature = "kotlin",
1505        feature = "lua",
1506        feature = "nix",
1507        feature = "php",
1508        feature = "python",
1509        feature = "ruby",
1510        feature = "rust",
1511        feature = "scala",
1512        feature = "solidity",
1513        feature = "swift",
1514        feature = "tsx",
1515        feature = "typescript",
1516        feature = "yaml"
1517    )
1518))]
1519impl Language for SupportLang {
1520    impl_lang_method!(kind_to_id, (kind: &str) => u16);
1521    impl_lang_method!(field_to_id, (field: &str) => Option<u16>);
1522    impl_lang_method!(meta_var_char, () => char);
1523    impl_lang_method!(expando_char, () => char);
1524    impl_lang_method!(extract_meta_var, (source: &str) => Option<MetaVariable>);
1525    impl_lang_method!(build_pattern, (builder: &PatternBuilder) => Result<Pattern, PatternError>);
1526    fn pre_process_pattern<'q>(&self, query: &'q str) -> Cow<'q, str> {
1527        execute_lang_method! { self, pre_process_pattern, query }
1528    }
1529    fn from_path<P: AsRef<Path>>(path: P) -> Option<Self> {
1530        from_extension(path.as_ref())
1531    }
1532}
1533
1534#[cfg(all(
1535    feature = "matching",
1536    any(
1537        feature = "all-parsers",
1538        feature = "napi-compatible",
1539        feature = "css-napi",
1540        feature = "html-napi",
1541        feature = "javascript-napi",
1542        feature = "typescript-napi",
1543        feature = "tsx-napi",
1544        feature = "bash",
1545        feature = "c",
1546        feature = "cpp",
1547        feature = "csharp",
1548        feature = "css",
1549        feature = "elixir",
1550        feature = "go",
1551        feature = "haskell",
1552        feature = "hcl",
1553        feature = "html",
1554        feature = "java",
1555        feature = "javascript",
1556        feature = "json",
1557        feature = "kotlin",
1558        feature = "lua",
1559        feature = "nix",
1560        feature = "php",
1561        feature = "python",
1562        feature = "ruby",
1563        feature = "rust",
1564        feature = "scala",
1565        feature = "solidity",
1566        feature = "swift",
1567        feature = "tsx",
1568        feature = "typescript",
1569        feature = "yaml"
1570    )
1571))]
1572impl LanguageExt for SupportLang {
1573    impl_lang_method!(get_ts_language, () => TSLanguage);
1574    impl_lang_method!(injectable_languages, () => Option<&'static [&'static str]>);
1575    fn extract_injections<L: LanguageExt>(
1576        &self,
1577        _root: Node<StrDoc<L>>,
1578    ) -> RapidMap<String, Vec<TSRange>> {
1579        match self {
1580            #[cfg(feature = "html-embedded")]
1581            SupportLang::Html => Html.extract_injections(_root),
1582            _ => RapidMap::default(),
1583        }
1584    }
1585}
1586
1587pub const fn extensions(lang: SupportLang) -> &'static [&'static str] {
1588    use SupportLang::*;
1589    match lang {
1590        #[cfg(any(feature = "bash", feature = "all-parsers"))]
1591        Bash => &constants::BASH_EXTS,
1592        #[cfg(any(feature = "c", feature = "all-parsers"))]
1593        C => &constants::C_EXTS,
1594        #[cfg(any(feature = "cpp", feature = "all-parsers"))]
1595        Cpp => &constants::CPP_EXTS,
1596        #[cfg(any(feature = "csharp", feature = "all-parsers"))]
1597        CSharp => &constants::CSHARP_EXTS,
1598        #[cfg(any(
1599            feature = "css",
1600            feature = "all-parsers",
1601            feature = "css-napi",
1602            feature = "napi-compatible"
1603        ))]
1604        Css => &constants::CSS_EXTS,
1605        #[cfg(any(feature = "elixir", feature = "all-parsers"))]
1606        Elixir => &constants::ELIXIR_EXTS,
1607        #[cfg(any(feature = "go", feature = "all-parsers"))]
1608        Go => &constants::GO_EXTS,
1609        #[cfg(any(feature = "haskell", feature = "all-parsers"))]
1610        Haskell => &constants::HASKELL_EXTS,
1611        #[cfg(any(feature = "hcl", feature = "all-parsers"))]
1612        Hcl => &constants::HCL_EXTS,
1613        #[cfg(any(
1614            feature = "html",
1615            feature = "all-parsers",
1616            feature = "html-napi",
1617            feature = "napi-compatible"
1618        ))]
1619        Html => &constants::HTML_EXTS,
1620        #[cfg(any(feature = "java", feature = "all-parsers"))]
1621        Java => &constants::JAVA_EXTS,
1622        #[cfg(any(
1623            feature = "javascript",
1624            feature = "all-parsers",
1625            feature = "javascript-napi",
1626            feature = "napi-compatible"
1627        ))]
1628        JavaScript => &constants::JAVASCRIPT_EXTS,
1629        #[cfg(any(feature = "json", feature = "all-parsers"))]
1630        Json => &constants::JSON_EXTS,
1631        #[cfg(any(feature = "kotlin", feature = "all-parsers"))]
1632        Kotlin => &constants::KOTLIN_EXTS,
1633        #[cfg(any(feature = "lua", feature = "all-parsers"))]
1634        Lua => &constants::LUA_EXTS,
1635        #[cfg(any(feature = "nix", feature = "all-parsers"))]
1636        Nix => &constants::NIX_EXTS,
1637        #[cfg(any(feature = "php", feature = "all-parsers"))]
1638        Php => &constants::PHP_EXTS,
1639        #[cfg(any(feature = "python", feature = "all-parsers"))]
1640        Python => &constants::PYTHON_EXTS,
1641        #[cfg(any(feature = "ruby", feature = "all-parsers"))]
1642        Ruby => &constants::RUBY_EXTS,
1643        #[cfg(any(feature = "rust", feature = "all-parsers"))]
1644        Rust => &constants::RUST_EXTS,
1645        #[cfg(any(feature = "scala", feature = "all-parsers"))]
1646        Scala => &constants::SCALA_EXTS,
1647        #[cfg(any(feature = "solidity", feature = "all-parsers"))]
1648        Solidity => &constants::SOLIDITY_EXTS,
1649        #[cfg(any(feature = "swift", feature = "all-parsers"))]
1650        Swift => &constants::SWIFT_EXTS,
1651        #[cfg(any(
1652            feature = "typescript",
1653            feature = "all-parsers",
1654            feature = "typescript-napi",
1655            feature = "napi-compatible"
1656        ))]
1657        TypeScript => &constants::TYPESCRIPT_EXTS,
1658        #[cfg(any(
1659            feature = "tsx",
1660            feature = "all-parsers",
1661            feature = "tsx-napi",
1662            feature = "napi-compatible"
1663        ))]
1664        Tsx => &constants::TSX_EXTS,
1665        #[cfg(any(feature = "yaml", feature = "all-parsers"))]
1666        Yaml => &constants::YAML_EXTS,
1667        #[cfg(not(any(
1668            feature = "all-parsers",
1669            feature = "napi-environment",
1670            feature = "napi-compatible",
1671            feature = "css-napi",
1672            feature = "html-napi",
1673            feature = "javascript-napi",
1674            feature = "typescript-napi",
1675            feature = "tsx-napi",
1676            feature = "bash",
1677            feature = "c",
1678            feature = "cpp",
1679            feature = "csharp",
1680            feature = "css",
1681            feature = "elixir",
1682            feature = "go",
1683            feature = "haskell",
1684            feature = "hcl",
1685            feature = "html",
1686            feature = "java",
1687            feature = "javascript",
1688            feature = "json",
1689            feature = "kotlin",
1690            feature = "lua",
1691            feature = "nix",
1692            feature = "php",
1693            feature = "python",
1694            feature = "ruby",
1695            feature = "rust",
1696            feature = "scala",
1697            feature = "solidity",
1698            feature = "swift",
1699            feature = "tsx",
1700            feature = "typescript",
1701            feature = "yaml"
1702        )))]
1703        NoEnabledLangs => &[],
1704    }
1705}
1706
1707/// Guess which programming language a file is written in
1708/// Adapt from `<https://github.com/Wilfred/difftastic/blob/master/src/parse/guess_language.rs>`
1709/// N.B do not confuse it with `FromStr` trait. This function is to guess language from file extension.
1710///
1711/// We check against the most common file types and extensions first.
1712/// These are hardcoded matches
1713#[inline]
1714pub fn from_extension(path: &Path) -> Option<SupportLang> {
1715    let ext = path.extension()?.to_str()?.to_ascii_lowercase();
1716    from_extension_str(&ext)
1717}
1718
1719#[inline]
1720pub fn from_extension_str(ext: &str) -> Option<SupportLang> {
1721    let ext = ext.to_ascii_lowercase();
1722    // TODO: Add shebang check if no ext
1723    if ext.is_empty() {
1724        return None;
1725    }
1726    match ext.as_str() {
1727        #[cfg(any(feature = "python", feature = "all-parsers"))]
1728        "py" => Some(SupportLang::Python),
1729        #[cfg(any(
1730            feature = "javascript",
1731            feature = "all-parsers",
1732            feature = "javascript-napi",
1733            feature = "napi-compatible"
1734        ))]
1735        "js" => Some(SupportLang::JavaScript),
1736        #[cfg(any(
1737            feature = "typescript",
1738            feature = "all-parsers",
1739            feature = "typescript-napi",
1740            feature = "napi-compatible"
1741        ))]
1742        "ts" => Some(SupportLang::TypeScript),
1743        #[cfg(any(feature = "java", feature = "all-parsers"))]
1744        "java" => Some(SupportLang::Java),
1745        #[cfg(any(feature = "go", feature = "all-parsers"))]
1746        "go" => Some(SupportLang::Go),
1747        #[cfg(any(feature = "cpp", feature = "all-parsers"))]
1748        "cpp" => Some(SupportLang::Cpp),
1749        #[cfg(any(feature = "rust", feature = "all-parsers"))]
1750        "rs" => Some(SupportLang::Rust),
1751        #[cfg(any(feature = "c", feature = "all-parsers"))]
1752        "c" => Some(SupportLang::C),
1753        // json and yaml are the most common config formats
1754        #[cfg(any(feature = "json", feature = "all-parsers"))]
1755        "json" => Some(SupportLang::Json),
1756        #[cfg(any(feature = "yaml", feature = "all-parsers"))]
1757        "yaml" | "yml" => Some(SupportLang::Yaml),
1758        _ => ext_iden::match_by_aho_corasick(&ext),
1759    }
1760}
1761
1762fn add_custom_file_type<'b>(
1763    builder: &'b mut TypesBuilder,
1764    file_type: &str,
1765    suffix_list: &[&str],
1766) -> &'b mut TypesBuilder {
1767    for suffix in suffix_list {
1768        let glob = format!("*.{suffix}");
1769        builder
1770            .add(file_type, &glob)
1771            .expect("file pattern must compile");
1772    }
1773    builder.select(file_type)
1774}
1775
1776fn file_types(lang: SupportLang) -> Types {
1777    let mut builder = TypesBuilder::new();
1778    let exts = extensions(lang);
1779    let lang_name = lang.to_string();
1780    add_custom_file_type(&mut builder, &lang_name, exts);
1781    builder.build().expect("file type must be valid")
1782}
1783
1784pub fn config_file_type() -> Types {
1785    let mut builder = TypesBuilder::new();
1786    let builder = add_custom_file_type(&mut builder, "yml", &["yml", "yaml"]);
1787    builder.build().expect("yaml type must be valid")
1788}
1789
1790#[cfg(test)]
1791mod test {
1792    use super::*;
1793    use thread_ast_engine::{Pattern, matcher::MatcherExt};
1794
1795    pub fn test_match_lang(query: &str, source: &str, lang: impl LanguageExt) {
1796        let cand = lang.ast_grep(source);
1797        let pattern = Pattern::new(query, &lang);
1798        assert!(
1799            pattern.find_node(cand.root()).is_some(),
1800            "goal: {pattern:?}, candidate: {}",
1801            cand.root().get_inner_node().to_sexp(),
1802        );
1803    }
1804
1805    pub fn test_non_match_lang(query: &str, source: &str, lang: impl LanguageExt) {
1806        let cand = lang.ast_grep(source);
1807        let pattern = Pattern::new(query, &lang);
1808        assert!(
1809            pattern.find_node(cand.root()).is_none(),
1810            "goal: {pattern:?}, candidate: {}",
1811            cand.root().get_inner_node().to_sexp(),
1812        );
1813    }
1814
1815    pub fn test_replace_lang(
1816        src: &str,
1817        pattern: &str,
1818        replacer: &str,
1819        lang: impl LanguageExt,
1820    ) -> String {
1821        let mut source = lang.ast_grep(src);
1822        assert!(
1823            source
1824                .replace(pattern, replacer)
1825                .expect("should parse successfully")
1826        );
1827        source.generate()
1828    }
1829
1830    #[test]
1831    fn test_js_string() {
1832        test_match_lang("'a'", "'a'", JavaScript);
1833        test_match_lang("\"\"", "\"\"", JavaScript);
1834        test_match_lang("''", "''", JavaScript);
1835    }
1836
1837    #[test]
1838    fn test_guess_by_extension() {
1839        let path = Path::new("foo.rs");
1840        assert_eq!(from_extension(path), Some(SupportLang::Rust));
1841    }
1842
1843    #[test]
1844    fn test_optimized_extension_matching() {
1845        // Test that the optimized implementation produces the same results as the original
1846        let test_cases = [
1847            ("main.rs", Some(SupportLang::Rust)),
1848            ("app.js", Some(SupportLang::JavaScript)),
1849            ("main.tf", Some(SupportLang::Hcl)),
1850            ("index.html", Some(SupportLang::Html)),
1851            ("data.json", Some(SupportLang::Json)),
1852            ("script.py", Some(SupportLang::Python)),
1853            ("main.go", Some(SupportLang::Go)),
1854            ("style.css", Some(SupportLang::Css)),
1855            ("component.tsx", Some(SupportLang::Tsx)),
1856            ("build.gradle.kts", Some(SupportLang::Kotlin)),
1857            ("somefile.nix", Some(SupportLang::Nix)),
1858            ("config.yml", Some(SupportLang::Yaml)),
1859            ("script.sh", Some(SupportLang::Bash)),
1860            ("app.swift", Some(SupportLang::Swift)),
1861            ("main.cpp", Some(SupportLang::Cpp)),
1862            ("header.hpp", Some(SupportLang::Cpp)),
1863            ("style.scss", Some(SupportLang::Css)),
1864            ("script.rb", Some(SupportLang::Ruby)),
1865            ("supercryptodude.sol", Some(SupportLang::Solidity)),
1866            ("main.scala", Some(SupportLang::Scala)),
1867            ("app.kt", Some(SupportLang::Kotlin)),
1868            // Case insensitive tests
1869            ("Main.RS", Some(SupportLang::Rust)),
1870            ("App.JS", Some(SupportLang::JavaScript)),
1871            ("Config.YML", Some(SupportLang::Yaml)),
1872            // Non-existent extensions
1873            ("file.xyz", None),
1874            ("test.unknown", None),
1875        ];
1876
1877        for (filename, expected) in test_cases {
1878            let path = Path::new(filename);
1879            let result = from_extension(path);
1880            assert_eq!(result, expected, "Failed for {}", filename);
1881
1882            // Also test the direct extension matching
1883            if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
1884                let direct_result = ext_iden::match_by_aho_corasick(ext);
1885                assert_eq!(
1886                    direct_result, expected,
1887                    "Direct matching failed for {}",
1888                    ext
1889                );
1890            }
1891        }
1892    }
1893
1894    // TODO: add test for file_types
1895}