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};