Skip to main content

big_code_analysis/macros/
mod.rs

1// `get_language!` is invoked only from feature-gated arms in `mk_lang!`
2// (one arm per `LANG::*` variant whose per-language Cargo feature is
3// enabled). A build with `--no-default-features` and no language
4// feature has no remaining call sites; suppress the lint for that
5// pathological-but-valid configuration.
6#[allow(unused_macros)]
7macro_rules! get_language {
8    (tree_sitter_typescript) => {
9        tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()
10    };
11    (tree_sitter_tsx) => {
12        tree_sitter_typescript::LANGUAGE_TSX.into()
13    };
14    (tree_sitter_php) => {
15        tree_sitter_php::LANGUAGE_PHP.into()
16    };
17    ($name:ident) => {
18        $name::LANGUAGE.into()
19    };
20}
21
22// `implement_metric_trait!` emits no-op `compute` bodies for every
23// metric / language pair listed. Every named-trait arm below
24// (`Abc`, `Cognitive`, `Halstead`, `Exit`, `Cyclomatic`, `Npa`,
25// `Npm`, `Loc`, `Wmc`) is silent: the metric will report 0 on every
26// input. The bracketed-trait arm (`[Trait]`) is different — it
27// emits an empty `impl Trait for X {}` and relies on the trait's
28// own default method body, which is correct for `Mi`, `Tokens`,
29// `Nom`, and `NArgs`.
30//
31// Audit: #188 walked every `(language, metric)` cell and classified
32// each as either a real default (the language has no construct the
33// metric measures) or a placeholder (the language HAS the construct
34// but no impl exists yet). Each invocation site carries a comment
35// recording the rationale and any follow-up issue number — keep
36// those comments in sync when you add a new language or land a real
37// impl.
38macro_rules! implement_metric_trait {
39    (Abc, $($code:ident),+) => (
40        implement_metric_trait!(@code_and_chain_taking Abc, $($code),+);
41    );
42    (Cognitive, $($code:ident),+) => (
43        $(
44           impl Cognitive for $code {
45               // No slot is ever written, so the walker must not
46               // pre-size a nesting map this grammar leaves empty.
47               const SEEDS_NESTING: bool = false;
48
49               fn compute<'a>(
50                   _node: &Node<'a>,
51                   _code: &'a [u8],
52                   _ancestors: crate::Ancestors<'a, '_>,
53                   _stats: &mut Stats,
54                   _nesting_map: &mut crate::spaces::NestingMap,
55               ) {}
56           }
57        )+
58    );
59    (Halstead, $($code:ident),+) => (
60        $(
61           impl Halstead for $code {
62               fn compute<'a>(
63                   _node: &Node<'a>,
64                   _code: &'a [u8],
65                   _ancestors: crate::Ancestors<'a, '_>,
66                   _halstead_maps: &mut HalsteadMaps<'a>,
67               ) {}
68           }
69        )+
70    );
71    // Internal helper: shared no-op body for traits whose `compute`
72    // signature is `<'a>(&Node<'a>, &'a [u8], Ancestors<'a, '_>,
73    // &mut Stats)` (Abc, Cyclomatic). Public arms below delegate here
74    // so the body is written once. `Npa` and `Npm` share the signature
75    // but need `HAS_MEMBERS = false` as well, so they route through
76    // `@code_and_chain_taking_memberless` instead — reaching for this
77    // arm for a new no-op `Npa` / `Npm` impl would silently restore
78    // the all-zero file-root block #1203 removed.
79    (@code_and_chain_taking $trait:ident, $($code:ident),+) => (
80        $(
81           impl $trait for $code {
82               fn compute<'a>(
83                   _node: &Node<'a>,
84                   _code: &'a [u8],
85                   _ancestors: crate::Ancestors<'a, '_>,
86                   _stats: &mut Stats,
87               ) {}
88           }
89        )+
90    );
91    // `Exit` is the one metric whose `compute` still takes no ancestor
92    // chain: no language's exit rule asks what encloses the node.
93    (Exit, $($code:ident),+) => (
94        $(
95           impl Exit for $code {
96               fn compute<'a>(_node: &Node<'a>, _code: &'a [u8], _stats: &mut Stats) {}
97           }
98        )+
99    );
100    (Cyclomatic, $($code:ident),+) => (
101        implement_metric_trait!(@code_and_chain_taking Cyclomatic, $($code),+);
102    );
103    // `Npa` and `Npm` take the same shape as the arm above plus one
104    // thing: the no-op impl must also opt the language out of
105    // *emitting* the block, which `HAS_MEMBERS` does. Without it a shell
106    // script would report `class_npa_sum: 0`, because the file unit is a
107    // member scope like any other and the walker would record its kind
108    // (#1203). `wmc` reaches the same place by different means — its
109    // no-op `compute` simply never records a kind.
110    (@code_and_chain_taking_memberless $trait:ident, $($code:ident),+) => (
111        $(
112           impl $trait for $code {
113               const HAS_MEMBERS: bool = false;
114
115               fn compute<'a>(
116                   _node: &Node<'a>,
117                   _code: &'a [u8],
118                   _ancestors: crate::Ancestors<'a, '_>,
119                   _stats: &mut Stats,
120               ) {}
121           }
122        )+
123    );
124    (Npa, $($code:ident),+) => (
125        implement_metric_trait!(@code_and_chain_taking_memberless Npa, $($code),+);
126    );
127    (Npm, $($code:ident),+) => (
128        implement_metric_trait!(@code_and_chain_taking_memberless Npm, $($code),+);
129    );
130    (Loc, $($code:ident),+) => (
131        $(
132           impl Loc for $code {
133               fn compute(
134                   _node: &Node,
135                   _ancestors: crate::Ancestors<'_, '_>,
136                   _stats: &mut Stats,
137                   _is_func_space: bool,
138               ) {}
139           }
140        )+
141    );
142    (Wmc, $($code:ident),+) => (
143        $(
144           impl Wmc for $code {
145               fn compute(_space_kind: SpaceKind, _cyclomatic: &cyclomatic::Stats, _stats: &mut Stats) {}
146           }
147        )+
148    );
149    ([$trait:ident], $($code:ident),+) => (
150        $(
151           impl $trait for $code {}
152        )+
153    );
154    ($trait:ident, $($code:ident),+) => (
155        $(
156           impl $trait for $code {
157               fn compute(_node: &Node, _stats: &mut Stats) {}
158           }
159        )+
160    )
161}
162
163macro_rules! mk_lang {
164    ( $( ($feature:literal, $camel:ident, $name:ident, $display: expr, $description:expr, $version:literal) ),* ) => {
165        /// The list of supported languages.
166        ///
167        /// Every variant is always defined regardless of the Cargo
168        /// feature set: per-language features only gate the grammar
169        /// crate references, never the enum surface itself. Disabled
170        /// variants surface at runtime as
171        /// [`crate::MetricsError::LanguageDisabled`] from every entry
172        /// point that returns a `Result`.
173        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
174        pub enum LANG {
175            $(
176                #[doc = $description]
177                $camel,
178            )*
179        }
180        impl LANG {
181            /// Return an iterator over the supported languages.
182            ///
183            /// # Examples
184            ///
185            /// ```
186            /// use big_code_analysis::LANG;
187            ///
188            /// for lang in LANG::into_enum_iter() {
189            ///     println!("{:?}", lang);
190            /// }
191            /// ```
192            pub fn into_enum_iter() -> impl Iterator<Item=LANG> {
193                use LANG::*;
194                [$( $camel, )*].into_iter()
195            }
196
197            /// Returns the name of a language as a `&str`.
198            ///
199            /// # Examples
200            ///
201            /// ```
202            /// use big_code_analysis::LANG;
203            ///
204            /// println!("{}", LANG::Rust.name());
205            /// ```
206            pub fn name(&self) -> &'static str {
207                match self {
208                    $(
209                        LANG::$camel => $display,
210                    )*
211                }
212            }
213
214            /// Returns the pinned tree-sitter grammar crate version that
215            /// backs this variant (e.g. `"0.25.1"` for [`LANG::Bash`]).
216            ///
217            /// The value mirrors the `=X.Y.Z` pin in the workspace
218            /// `Cargo.toml` and is independent of the per-language Cargo
219            /// feature: it is returned even for a variant whose feature is
220            /// disabled in the current build (a build-time constant, no
221            /// grammar crate reference). A drift test in `src/langs.rs`
222            /// asserts every value here matches the manifest pin.
223            ///
224            /// # Grammars vs. forks
225            ///
226            /// For languages backed by an upstream crates.io grammar
227            /// (`bash`, `rust`, `python`, `typescript`, …) this is the
228            /// exact upstream grammar version, so a consumer migrating
229            /// matchers off py-tree-sitter can line node-kind vocabularies
230            /// up against the same pin. For the vendored big-code-analysis
231            /// forks (`mozcpp`, `mozjs`, `tcl`, `ccomment`, `preproc`,
232            /// `kotlin`) the value is the **fork crate's** version
233            /// (published as `bca-tree-sitter-*` / `tree-sitter-kotlin-ng`),
234            /// not an upstream tree-sitter grammar semver — there is no
235            /// upstream release to compare against.
236            ///
237            /// This is part of the value-not-stable surface: the returned
238            /// version changes whenever the grammar pin is bumped.
239            #[must_use]
240            pub fn grammar_version(&self) -> &'static str {
241                match self {
242                    $(
243                        LANG::$camel => $version,
244                    )*
245                }
246            }
247
248            /// Reports whether this variant's grammar crate is
249            /// compiled into the current build.
250            ///
251            /// Returns `false` for variants whose per-language Cargo
252            /// feature is disabled; calling
253            /// [`Self::tree_sitter_language`], [`crate::analyze`],
254            /// or any other dispatcher with such a variant will
255            /// return [`crate::MetricsError::LanguageDisabled`].
256            #[must_use]
257            pub fn is_enabled(&self) -> bool {
258                match self {
259                    $(
260                        #[cfg(feature = $feature)]
261                        LANG::$camel => true,
262                        #[cfg(not(feature = $feature))]
263                        LANG::$camel => false,
264                    )*
265                }
266            }
267
268            // Returns a tree-sitter language paired with this variant,
269            // or `Err(LanguageDisabled)` when the matching Cargo
270            // feature is off. This is the internal entry point used
271            // by `Tree::new` to construct a parser; the public
272            // counterpart is `tree_sitter_language`.
273            pub(crate) fn get_ts_language(&self) -> Result<Language, crate::MetricsError> {
274                match self {
275                    $(
276                        #[cfg(feature = $feature)]
277                        LANG::$camel => Ok(get_language!($name)),
278                        #[cfg(not(feature = $feature))]
279                        LANG::$camel => Err(crate::MetricsError::LanguageDisabled(*self)),
280                    )*
281                }
282            }
283
284            /// Returns the [`tree_sitter::Language`] grammar used by
285            /// this variant.
286            ///
287            /// Useful when feeding a caller-built
288            /// [`tree_sitter::Parser`] into the
289            /// [`crate::Ast::from_tree_sitter`] entry point — the
290            /// language returned here is the one the metric walker
291            /// expects for `kind_id` matching, so the trees agree
292            /// structurally.
293            ///
294            /// This method is part of the value-not-stable surface:
295            /// the underlying `tree-sitter-*` grammar pin may bump
296            /// in any minor release, which can change `Language`
297            /// equality on the caller side.
298            ///
299            /// # Errors
300            ///
301            /// Returns [`crate::MetricsError::LanguageDisabled`] when
302            /// the variant's per-language Cargo feature is not
303            /// enabled in the current build (see the `[features]`
304            /// table in the root `Cargo.toml`).
305            ///
306            /// # Examples
307            ///
308            /// ```
309            /// use big_code_analysis::LANG;
310            ///
311            /// let _lang = LANG::Rust.tree_sitter_language().expect("rust feature enabled");
312            /// ```
313            pub fn tree_sitter_language(&self) -> Result<::tree_sitter::Language, crate::MetricsError> {
314                self.get_ts_language()
315            }
316        }
317
318        /// Renders the language's canonical lowercase slug, identical to
319        /// [`LANG::name`].
320        ///
321        /// Every variant has a distinct slug, so `Display` is injective
322        /// and a `Display` → [`FromStr`](std::str::FromStr) round-trip
323        /// returns the original variant (see the round-trip test in
324        /// `src/langs.rs`). The slug is the single canonical identifier
325        /// used across every surface (CLI JSON, web `/metrics`, the
326        /// Python bindings): it contains no punctuation and is always a
327        /// valid `FromStr` lookup token.
328        impl ::std::fmt::Display for LANG {
329            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
330                f.write_str(self.name())
331            }
332        }
333
334        /// Parses a [`LANG`] from its [`Display`](std::fmt::Display)
335        /// spelling (the canonical lowercase [`LANG::name`] slug, e.g.
336        /// `"rust"`, `"cpp"`, `"csharp"`, `"tsx"`).
337        ///
338        /// Matching is case-sensitive and exact, mirroring
339        /// [`Metric`](crate::Metric)'s `FromStr`: only the canonical
340        /// lowercase slug is accepted. File extensions and emacs modes
341        /// are deliberately *not* accepted here — use
342        /// [`get_from_ext`](crate::get_from_ext) /
343        /// [`get_from_emacs_mode`](crate::get_from_emacs_mode) for those.
344        ///
345        /// Every variant has a distinct slug, so this is the exact
346        /// inverse of [`Display`](std::fmt::Display): the round-trip
347        /// `LANG::from_str(&lang.to_string())` returns the original
348        /// variant for every `LANG`.
349        impl ::std::str::FromStr for LANG {
350            type Err = $crate::macros::ParseLangError;
351
352            fn from_str(s: &str) -> Result<Self, Self::Err> {
353                LANG::into_enum_iter()
354                    .find(|lang| lang.name() == s)
355                    .ok_or_else(|| $crate::macros::ParseLangError::new(s))
356            }
357        }
358    };
359}
360
361/// Error returned by [`LANG`](crate::LANG)'s
362/// [`FromStr`](std::str::FromStr) impl when the input is not a
363/// recognised language name.
364///
365/// Holds the offending input verbatim so wrapper layers can format
366/// their own user-facing message; mirrors
367/// [`ParseMetricError`](crate::ParseMetricError).
368#[derive(Debug, Clone, PartialEq, Eq)]
369pub struct ParseLangError(String);
370
371impl ParseLangError {
372    // Constructor kept `pub(crate)` so the macro-generated `FromStr`
373    // impl in `src/langs.rs` can build the error without exposing the
374    // private field across module boundaries.
375    pub(crate) fn new(input: &str) -> Self {
376        Self(input.to_owned())
377    }
378
379    /// The rejected input that failed to parse as a language name.
380    ///
381    /// Lets callers recover the offending string programmatically
382    /// rather than scraping it out of the [`Display`](std::fmt::Display)
383    /// output. Mirrors
384    /// [`ParseMetricError::input`](crate::ParseMetricError::input).
385    #[must_use]
386    pub fn input(&self) -> &str {
387        &self.0
388    }
389}
390
391impl ::std::fmt::Display for ParseLangError {
392    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
393        write!(f, "unknown language: {}", self.0)
394    }
395}
396
397impl ::std::error::Error for ParseLangError {}
398
399macro_rules! mk_action {
400    ( $( ($feature:literal, $camel:ident, $parser:ident) ),* ) => {
401        /// Language-dispatched bundle of a parsed tree plus its
402        /// source bytes, one variant per Cargo-feature-enabled
403        /// language. The public seam is [`crate::Ast`]; this enum is
404        /// the macro-generated internal carrier it wraps.
405        ///
406        /// With every per-language feature disabled this enum is a
407        /// 0-variant uninhabited type. Each method below therefore
408        /// terminates its `match self` with a
409        /// `#[cfg(not(any(feature = …)))] _ => match *self {}` arm:
410        /// stable Rust treats `&UninhabitedType` as inhabited (E0004),
411        /// so the outer match needs a wildcard, and `match *self {}`
412        /// is exhaustive over the uninhabited dereferenced value —
413        /// divergent, no panic, no `unsafe`, statically unreachable in
414        /// safe code because the public seam `crate::Ast` has only
415        /// fallible constructors that return `Err(LanguageDisabled)`
416        /// for every `LANG` variant under that build.
417        ///
418        /// When a method takes by-value parameters (see
419        /// [`Self::run_metrics`]), prefix the divergent arm with
420        /// `let _ = (param1, param2, …);` to silence
421        /// `unused_variables` under `RUSTFLAGS=-D warnings` — the
422        /// `match *self {}` body is `!`, so the consumed values are
423        /// never actually dropped at runtime.
424        pub(crate) enum AstInner {
425            $(
426                #[cfg(feature = $feature)]
427                $camel($parser),
428            )*
429        }
430
431        impl AstInner {
432            /// Run the metric walker against the held parse. The
433            /// caller passes `name` and `options` per call so a
434            /// single `AstInner` can be reused with different metric
435            /// subsets.
436            pub(crate) fn run_metrics(
437                &self,
438                name: Option<String>,
439                options: MetricsOptions,
440            ) -> Result<FuncSpace, MetricsError> {
441                match self {
442                    $(
443                        #[cfg(feature = $feature)]
444                        AstInner::$camel(parser) => metrics_inner(parser, name, options),
445                    )*
446                    #[cfg(not(any( $( feature = $feature ),* )))]
447                    _ => {
448                        let _ = (name, options);
449                        match *self {}
450                    },
451                }
452            }
453
454            /// Run the operator/operand walk against the held parse,
455            /// carrying an explicit `name` end-to-end. Backs
456            /// [`crate::Ast::ops`]; the ops analogue of [`Self::run_metrics`].
457            pub(crate) fn run_ops(
458                &self,
459                name: Option<String>,
460            ) -> Result<Ops, MetricsError> {
461                match self {
462                    $(
463                        #[cfg(feature = $feature)]
464                        AstInner::$camel(parser) => ops_inner(parser, name),
465                    )*
466                    #[cfg(not(any( $( feature = $feature ),* )))]
467                    _ => {
468                        let _ = name;
469                        match *self {}
470                    },
471                }
472            }
473
474            /// Strip comments from the held parse. Backs
475            /// [`crate::Ast::strip_comments`]; the comment-removal analogue
476            /// of [`Self::run_ops`].
477            pub(crate) fn run_strip_comments(&self) -> Option<Vec<u8>> {
478                match self {
479                    $(
480                        #[cfg(feature = $feature)]
481                        AstInner::$camel(parser) => crate::comment_rm::rm_comments(parser),
482                    )*
483                    #[cfg(not(any( $( feature = $feature ),* )))]
484                    _ => match *self {},
485                }
486            }
487
488            /// Detect the span of every function in the held parse. Backs
489            /// [`crate::Ast::functions`].
490            pub(crate) fn run_functions(&self) -> Vec<crate::FunctionSpan> {
491                match self {
492                    $(
493                        #[cfg(feature = $feature)]
494                        AstInner::$camel(parser) => crate::function::function(parser),
495                    )*
496                    #[cfg(not(any( $( feature = $feature ),* )))]
497                    _ => match *self {},
498                }
499            }
500
501            /// Build the AST dump for the held parse under `cfg`. Backs
502            /// [`crate::Ast::dump`].
503            pub(crate) fn run_dump(&self, cfg: crate::AstCfg) -> crate::AstResponse {
504                match self {
505                    $(
506                        #[cfg(feature = $feature)]
507                        AstInner::$camel(parser) => crate::ast::dump_inner(parser, cfg),
508                    )*
509                    #[cfg(not(any( $( feature = $feature ),* )))]
510                    _ => {
511                        let _ = cfg;
512                        match *self {}
513                    },
514                }
515            }
516
517            /// Count `(matching, total)` nodes for `filters` in the held
518            /// parse. Backs [`crate::Ast::count`].
519            pub(crate) fn run_count(&self, filters: &[String]) -> (usize, usize) {
520                match self {
521                    $(
522                        #[cfg(feature = $feature)]
523                        AstInner::$camel(parser) => crate::count::count(parser, filters),
524                    )*
525                    #[cfg(not(any( $( feature = $feature ),* )))]
526                    _ => {
527                        let _ = filters;
528                        match *self {}
529                    },
530                }
531            }
532
533            /// Find every node matching `filters` in the held parse. Backs
534            /// [`crate::Ast::find`]; the returned nodes borrow the held tree.
535            pub(crate) fn run_find(
536                &self,
537                filters: &[String],
538            ) -> Result<Vec<crate::Node<'_>>, MetricsError> {
539                match self {
540                    $(
541                        #[cfg(feature = $feature)]
542                        AstInner::$camel(parser) => crate::find::find(parser, filters),
543                    )*
544                    #[cfg(not(any( $( feature = $feature ),* )))]
545                    _ => {
546                        let _ = filters;
547                        match *self {}
548                    },
549                }
550            }
551
552            /// Collect every in-source suppression marker in the held parse.
553            /// Backs [`crate::Ast::suppressions`].
554            pub(crate) fn run_suppressions(&self) -> Vec<crate::SuppressionMarker> {
555                match self {
556                    $(
557                        #[cfg(feature = $feature)]
558                        AstInner::$camel(parser) => crate::suppression::suppression_markers(parser),
559                    )*
560                    #[cfg(not(any( $( feature = $feature ),* )))]
561                    _ => match *self {},
562                }
563            }
564
565            /// Borrow the root [`crate::Node`] of the held parse. Backs
566            /// [`crate::Ast::root_node`].
567            pub(crate) fn root_node(&self) -> crate::Node<'_> {
568                match self {
569                    $(
570                        #[cfg(feature = $feature)]
571                        AstInner::$camel(parser) => parser.root(),
572                    )*
573                    #[cfg(not(any( $( feature = $feature ),* )))]
574                    _ => match *self {},
575                }
576            }
577
578            pub(crate) fn language(&self) -> LANG {
579                match self {
580                    $(
581                        #[cfg(feature = $feature)]
582                        AstInner::$camel(_) => LANG::$camel,
583                    )*
584                    #[cfg(not(any( $( feature = $feature ),* )))]
585                    _ => match *self {},
586                }
587            }
588
589            pub(crate) fn code_bytes(&self) -> &[u8] {
590                match self {
591                    $(
592                        #[cfg(feature = $feature)]
593                        AstInner::$camel(parser) => parser.code(),
594                    )*
595                    #[cfg(not(any( $( feature = $feature ),* )))]
596                    _ => match *self {},
597                }
598            }
599
600            pub(crate) fn ts_tree(&self) -> &::tree_sitter::Tree {
601                match self {
602                    $(
603                        #[cfg(feature = $feature)]
604                        AstInner::$camel(parser) => parser.ts_tree(),
605                    )*
606                    #[cfg(not(any( $( feature = $feature ),* )))]
607                    _ => match *self {},
608                }
609            }
610        }
611
612        /// Internal parse-dispatch shim that backs [`crate::Ast::parse`].
613        /// Lives in the `mk_action!` macro so each new language only
614        /// has to declare its parser tag once.
615        pub(crate) fn ast_parse_dispatch(
616            lang: LANG,
617            source: Vec<u8>,
618            preproc_path: Option<&Path>,
619            preproc: Option<Arc<PreprocResults>>,
620        ) -> Result<AstInner, MetricsError> {
621            // `Parser::new` keys the C++ macro-expansion lookup off the
622            // caller-supplied path; for callers analysing in-memory
623            // snippets with no preprocessor path, fall back to an
624            // empty `Path` ("") which the lookup ignores. The empty
625            // path is *not* leaked into `FuncSpace::name` — that
626            // is carried separately on `Ast`. `source` is taken by value
627            // so an owned `Source` (`Source::from_bytes`) moves its
628            // buffer straight into the parser instead of copying it.
629            let preproc_path = preproc_path.unwrap_or(Path::new(""));
630            match lang {
631                $(
632                    #[cfg(feature = $feature)]
633                    LANG::$camel => Ok(AstInner::$camel($parser::new(source, preproc_path, preproc))),
634                    #[cfg(not(feature = $feature))]
635                    LANG::$camel => {
636                        let _ = (source, preproc_path, preproc);
637                        Err(MetricsError::LanguageDisabled(lang))
638                    },
639                )*
640            }
641        }
642
643        /// Internal tree-adoption dispatch that backs
644        /// [`crate::Ast::from_tree_sitter`].
645        pub(crate) fn ast_from_tree_dispatch(
646            lang: LANG,
647            tree: ::tree_sitter::Tree,
648            source: Vec<u8>,
649        ) -> Result<AstInner, MetricsError> {
650            match lang {
651                $(
652                    #[cfg(feature = $feature)]
653                    LANG::$camel => Ok(AstInner::$camel($parser::from_tree(tree, source))),
654                    #[cfg(not(feature = $feature))]
655                    LANG::$camel => {
656                        let _ = (tree, source);
657                        Err(MetricsError::LanguageDisabled(lang))
658                    },
659                )*
660            }
661        }
662
663    };
664}
665
666macro_rules! mk_extensions {
667    ( $( ($camel:ident, [ $( $ext:ident ),* ]) ),* ) => {
668        /// Detects the language associated to the input file extension.
669        ///
670        /// # Examples
671        ///
672        /// ```
673        /// use big_code_analysis::get_from_ext;
674        ///
675        /// let ext = "rs";
676        ///
677        /// get_from_ext(ext).unwrap();
678        /// ```
679        pub fn get_from_ext(ext: &str) -> Option<LANG>{
680            match ext {
681                $(
682                    $(
683                        stringify!($ext) => Some(LANG::$camel),
684                    )*
685                )*
686                _ => None,
687            }
688        }
689
690        impl LANG {
691            /// Returns the file extensions recognised for this language.
692            ///
693            /// The returned list is the same one consulted by
694            /// [`get_from_ext`] and [`crate::get_language_for_file`].
695            /// Helper variants without user-facing files (`Ccomment`,
696            /// `Preproc`) return an empty slice.
697            ///
698            /// # Examples
699            ///
700            /// ```
701            /// use big_code_analysis::LANG;
702            ///
703            /// assert!(LANG::Rust.extensions().contains(&"rs"));
704            /// ```
705            #[must_use]
706            pub fn extensions(&self) -> &'static [&'static str] {
707                match self {
708                    $(
709                        LANG::$camel => &[ $( stringify!($ext), )* ],
710                    )*
711                }
712            }
713        }
714    };
715}
716
717macro_rules! mk_emacs_mode {
718    ( $( ($camel:ident, [ $( $emacs_mode:expr ),* ]) ),* ) => {
719        /// Detects the language associated to the input `Emacs` mode.
720        ///
721        /// An `Emacs` mode is used to detect a language according to
722        /// particular text-information contained in a file.
723        ///
724        /// # Examples
725        ///
726        /// ```
727        /// use big_code_analysis::get_from_emacs_mode;
728        ///
729        /// let emacs_mode = "rust";
730        ///
731        /// get_from_emacs_mode(emacs_mode).unwrap();
732        /// ```
733        pub fn get_from_emacs_mode(mode: &str) -> Option<LANG>{
734            match mode {
735                $(
736                    $(
737                        $emacs_mode => Some(LANG::$camel),
738                    )*
739                )*
740                _ => None,
741            }
742        }
743    };
744}
745
746macro_rules! mk_code {
747    ( $( ($camel:ident, $code:ident, $parser:ident, $name:ident, $docname:expr) ),* ) => {
748        $(
749            #[doc = concat!("Per-language code type tag for ", $docname, "; carries no data.")]
750            pub(crate) struct $code { _guard: (), }
751
752            impl LanguageInfo for $code {
753                type BaseLang = $camel;
754
755                fn lang() -> LANG {
756                    LANG::$camel
757                }
758            }
759
760            #[doc = "The `"]
761            #[doc = $docname]
762            #[doc = "` language parser."]
763            pub(crate) type $parser = Parser<$code>;
764        )*
765    };
766}
767
768macro_rules! mk_langs {
769    ( $( ($feature:literal, $camel:ident, $description: expr, $display: expr, $code:ident, $parser:ident, $name:ident, [ $( $ext:ident ),* ], [ $( $emacs_mode:expr ),* ], $version:literal) ),* ) => {
770        mk_lang!($( ($feature, $camel, $name, $display, $description, $version) ),*);
771        mk_action!($( ($feature, $camel, $parser) ),*);
772        mk_extensions!($( ($camel, [ $( $ext ),* ]) ),*);
773        mk_emacs_mode!($( ($camel, [ $( $emacs_mode ),* ]) ),*);
774        mk_code!($( ($camel, $code, $parser, $name, stringify!($camel)) ),*);
775    };
776}
777
778mod kind_sets;
779
780pub(crate) use implement_metric_trait;
781pub(crate) use kind_sets::{
782    cpp_bool_terminal_kinds, csharp_bool_terminal_kinds, csharp_invocation_expr_kinds,
783    csharp_paren_expr_kinds, csharp_prefix_unary_expr_kinds, csharp_var_decl_kinds,
784    csharp_var_declarator_kinds, elixir_bool_terminal_kinds, go_bool_terminal_kinds,
785    groovy_bool_terminal_kinds, irules_bool_terminal_kinds, java_bool_terminal_kinds,
786    javascript_bool_terminal_kinds, kotlin_bool_terminal_kinds, lua_bool_terminal_kinds,
787    mozjs_bool_terminal_kinds, perl_bool_terminal_kinds, php_bool_terminal_kinds,
788    python_bool_terminal_kinds, ruby_bool_terminal_kinds, rust_bool_terminal_kinds,
789    tcl_bool_terminal_kinds, tsx_bool_terminal_kinds, typescript_bool_terminal_kinds,
790};
791pub(crate) use {
792    get_language, mk_action, mk_code, mk_emacs_mode, mk_extensions, mk_lang, mk_langs,
793};