big-code-analysis 2.4.0

Tool to compute and export code metrics
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
//! Cross-language `Npa` helper functions shared by the per-language
//! submodules and by sibling metrics (`npm`, `checker`).
//!
//! The public metric `Stats`, the `Npa` trait, and the
//! `impl_npa_java_like!` / `ts_npa_compute!` / `ts_member_is_public!` /
//! `js_npa_compute!` macros stay in the parent module; only the
//! cross-language predicate helpers live here so the parent clears the
//! 800-SLOC self-scan limit (#976).
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
#![allow(
    clippy::cast_precision_loss,
    clippy::cast_possible_truncation,
    clippy::cast_sign_loss
)]

use super::*;

// Distinguishes interface-like containers (interface, trait, annotation
// type) — whose members are implicitly public — from class-like
// containers (class, enum, record) that need an explicit `public`
// modifier. The dekobon grammar models all of these bodies as
// `class_body`, so the discriminant lives on the parent. Shared with
// `impl Npm for GroovyCode` (`metrics::npm`).
pub(crate) fn groovy_body_is_interface_like<'a>(
    body: &Node<'a>,
    ancestors: Ancestors<'a, '_>,
) -> bool {
    use Groovy::*;
    ancestors.parent(body).is_some_and(|p| {
        matches!(
            p.kind_id().into(),
            InterfaceDeclaration | TraitDeclaration | AnnotationTypeDeclaration
        )
    })
}

// Detects an explicit `public` modifier on a class member declaration.
// The dekobon grammar flattens the `_modifier` rule, so modifier
// tokens appear as direct children of the declaration — no `Modifiers`
// wrapper to descend into. Shared with `impl Npm for GroovyCode`.
pub(crate) fn groovy_has_explicit_public(declaration: &Node) -> bool {
    declaration.first_child(|id| id == Groovy::Public).is_some()
}

// C# uses individual `Modifier` nodes (not wrapped under a single
// `modifiers` node like Java); detecting `public` requires scanning
// every Modifier child of the declaration for a `public` keyword.
//
// The wrapper-plus-leaf shape this and the sibling below assume is the
// only one that reaches them. Two other rules alias a bare keyword to
// `modifier`, which makes those childless (#1418):
// `_parameter_type_with_modifiers` (six keywords, under `parameter`)
// and `_lambda_expression_init` / `anonymous_method_expression`
// (`static` / `async`, under a lambda). Neither is a direct child of a
// declaration — `parameter_list` intervenes for the first, and a lambda
// is an expression — and both helpers are handed a class member or an
// `accessor_declaration` only. They also use `first_child`, so a
// childless node would answer `false` rather than mis-answer even if
// one did arrive.
pub(crate) fn csharp_is_explicit_public(declaration: &Node) -> bool {
    declaration.children().any(|child| {
        matches!(child.kind_id().into(), Csharp::Modifier)
            && child.first_child(|id| id == Csharp::Public).is_some()
    })
}

// A C# member or accessor node carries an explicit visibility-narrowing
// modifier (`private` / `protected`). The single source of truth for the
// C# narrowing rule, shared by the npa interface-member gate below and the
// npm accessor gate (`csharp_member_public_method_count`).
pub(crate) fn csharp_node_has_private_or_protected_modifier(node: &Node) -> bool {
    node.children().any(|child| {
        matches!(child.kind_id().into(), Csharp::Modifier)
            && child
                .first_child(|id| id == Csharp::Private || id == Csharp::Protected)
                .is_some()
    })
}

// C# 8+ interface members default to public, so a field is a public
// attribute UNLESS it carries an explicit `private` or `protected`
// modifier. This is the inverse of `csharp_is_explicit_public` (the
// class rule, which requires an explicit `public`): missing modifier
// means public here, matching `ts_member_is_public!`'s default-public
// gate for TypeScript class members.
pub(crate) fn csharp_interface_member_is_public(declaration: &Node) -> bool {
    !csharp_node_has_private_or_protected_modifier(declaration)
}

// PHP's strict-explicit visibility rule (mirroring Java's pattern): a
// declaration is treated as public only when it carries an explicit
// `public` modifier. Modifier-less declarations — deprecated for
// properties since PHP 8 and merely conventional for methods — are NOT
// counted, even though PHP semantically defaults methods to public.
pub(crate) fn php_is_explicit_public(declaration: &Node) -> bool {
    declaration.children().any(|child| {
        matches!(child.kind_id().into(), Php::VisibilityModifier)
            && child.first_child(|id| id == Php::Public).is_some()
    })
}

// Named arguments of a Ruby `Call`, in source order. The grammar hangs
// them off an `argument_list` — aliased to two visible ids — whose
// children include the `(`, `)` and `,` tokens of the parenthesised
// spelling, so anonymous nodes are filtered out. A call written without
// an argument list (`private`, spelled as a `Call` only when it has a
// receiver) yields nothing.
pub(crate) fn ruby_call_named_arguments<'a>(call: &Node<'a>) -> impl Iterator<Item = Node<'a>> {
    use Ruby::*;

    call.children()
        .find(|c| matches!(c.kind_id().into(), ArgumentList | ArgumentList2))
        .into_iter()
        .flat_map(|args| args.children().filter(Node::is_named))
}

// How many attributes / methods one argument to an `attr_*` or
// visibility macro names. A `%i[a b]` word array is one argument naming
// several; every other symbol spelling names exactly one. `HashKeySymbol`
// (`a:`) reaches no argument list at the pinned grammar — it is only ever
// a `pair` or `keyword_pattern` key — but is kept as a defensive arm
// (grammar-dispatch rule 2), pinned by
// `ruby_hash_key_symbol_declares_no_attribute`.
fn ruby_symbol_argument_count(arg: &Node) -> usize {
    use Ruby::*;

    match arg.kind_id().into() {
        SimpleSymbol | DelimitedSymbol | HashKeySymbol | BareSymbol => 1,
        SymbolArray => arg
            .children()
            .filter(|c| matches!(c.kind_id().into(), BareSymbol))
            .count(),
        _ => 0,
    }
}

// Counts the number of symbol arguments passed to an `attr_accessor` /
// `attr_reader` / `attr_writer` macro `Call` node. `attr_accessor :a,
// :b, :c` exposes three attributes; an `attr_*` call with no arguments
// is ill-formed Ruby but defensively returns zero rather than one.
pub(crate) fn ruby_attr_macro_symbol_count(call: &Node) -> usize {
    ruby_call_named_arguments(call)
        .map(|arg| ruby_symbol_argument_count(&arg))
        .sum()
}

// The method name a `:foo` argument to `private` / `public` / … names.
// `simple_symbol` carries the whole literal including the leading colon
// (`:ok?`, `:==`); `delimited_symbol` (`:"val="`) and the `bare_symbol`
// elements of a `%i[…]` array both wrap their text in a `string_content`
// child. A `%i[…]` array names several methods rather than one, so its
// callers expand it into these elements first. Every other argument
// shape — a splat (`private *SYMS`), a bare identifier (`private foo`),
// a string (`private "foo"`) — names nothing this walk can resolve and
// yields `None` (#1399).
//
// A delimited symbol is resolvable only when `string_content` is its
// *whole* content: `:"get_#{suffix}"` also carries an `interpolation`
// child, and returning the literal half of it would name — and demote —
// a different method called `get_`.
pub(crate) fn ruby_symbol_name<'a>(node: &Node<'a>, source: &'a [u8]) -> Option<&'a str> {
    match node.kind_id().into() {
        Ruby::SimpleSymbol => node.utf8_text(source)?.strip_prefix(':'),
        Ruby::DelimitedSymbol | Ruby::BareSymbol => {
            let mut content = node.children().filter(Node::is_named);
            let first = content.next()?;
            (first.kind_id() == Ruby::StringContent as u16 && content.next().is_none())
                .then_some(first)?
                .utf8_text(source)
        }
        _ => None,
    }
}

// The declared name of a Ruby `method` / `singleton_method`. Read from
// the grammar's required `name` field rather than by child index: the
// name is the *second* identifier of `def obj.x`, and it is a `setter`
// (`val=`) or an `operator` (`==`) node rather than an `identifier` for
// those two spellings (grammar-dispatch rule 3).
pub(crate) fn ruby_method_name<'a>(method: &Node<'a>, source: &'a [u8]) -> Option<&'a str> {
    method.child_by_field_name("name")?.utf8_text(source)
}

// Ruby class-body visibility state. `private` / `public` / `protected`
// keywords flip this flag for every subsequent declaration in the same
// body until another marker overrides them. The default at the top of
// every class body is `Public`.
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum RubyVisibility {
    Public,
    Private,
    Protected,
}

// Recognises a bare visibility-keyword `identifier` child of a Ruby
// class body (`private` / `public` / `protected` with no arguments).
// tree-sitter-ruby emits the keyword-form as a literal `identifier`
// token; the argument-form (`private :foo`, `private def bar`) is a
// `Call` node instead and does NOT flip the body-wide flag.
pub(crate) fn ruby_visibility_marker(node: &Node, source: &[u8]) -> Option<RubyVisibility> {
    if !matches!(node.kind_id().into(), Ruby::Identifier) {
        return None;
    }
    match node.utf8_text(source)? {
        "private" => Some(RubyVisibility::Private),
        "public" => Some(RubyVisibility::Public),
        "protected" => Some(RubyVisibility::Protected),
        _ => None,
    }
}

// A Ruby visibility keyword used in call position. `targets_singleton`
// records *which* method family the keyword names: the bare keywords
// govern instance methods only, and demoting a `def self.x` takes the
// dedicated `private_class_method` / `public_class_method` pair (#1255).
#[derive(Clone, Copy)]
pub(crate) struct RubyVisibilityCall {
    pub(crate) visibility: RubyVisibility,
    pub(crate) targets_singleton: bool,
}

impl RubyVisibilityCall {
    // Whether this keyword names the given method family, and so decides
    // the visibility of a declaration in its argument list. `private def
    // self.x` does not govern the singleton it wraps, and
    // `private_class_method def x` does not govern the instance method.
    pub(crate) fn governs(self, singleton: bool) -> bool {
        self.targets_singleton == singleton
    }
}

// What a visibility-keyword `Call` in a Ruby class body does. Shared by
// `Npm` and `Npa` so the two walkers cannot drift on the same Ruby rule
// (grammar-dispatch rule 7).
pub(crate) enum RubyVisibilityEffect {
    // `private()` — the explicit-parens spelling of the bare keyword,
    // which sets the default for every later instance-level declaration
    // exactly as `private` on its own line does. (The bare spelling is
    // an `identifier`, not a `Call`, and is matched by
    // `ruby_visibility_marker`.)
    Flag(RubyVisibility),
    // `private def x` / `private :foo` / `private attr_accessor :b` —
    // the keyword governs only what its argument list names, leaving the
    // body-wide flag alone.
    Arguments(RubyVisibilityCall),
}

// Classifies a Ruby class-body `Call` whose callee is a visibility
// keyword.
//
// An argument-less `private_class_method` is an ArgumentError in Ruby
// rather than a flag flip, so it yields no effect at all.
pub(crate) fn ruby_visibility_effect(call: &Node, source: &[u8]) -> Option<RubyVisibilityEffect> {
    let (visibility, targets_singleton) = match ruby_callee_name(call, source)? {
        "private" => (RubyVisibility::Private, false),
        "public" => (RubyVisibility::Public, false),
        "protected" => (RubyVisibility::Protected, false),
        "private_class_method" => (RubyVisibility::Private, true),
        "public_class_method" => (RubyVisibility::Public, true),
        _ => return None,
    };
    if ruby_call_named_arguments(call).next().is_none() {
        return (!targets_singleton).then_some(RubyVisibilityEffect::Flag(visibility));
    }
    Some(RubyVisibilityEffect::Arguments(RubyVisibilityCall {
        visibility,
        targets_singleton,
    }))
}

// Whether a declaration counts as public when no visibility keyword
// names it: an instance method or attribute takes the body-wide flag,
// while a singleton method (`def self.x`) ignores that flag entirely —
// Ruby's `private` governs instance methods only (#1255).
pub(crate) fn ruby_declaration_is_public(singleton: bool, body_flag: RubyVisibility) -> bool {
    singleton || body_flag == RubyVisibility::Public
}

// Whether a declaration nested in a visibility call's argument list is
// public. The keyword governs it only when it names that method family,
// so `private def self.x` leaves the singleton alone and
// `private_class_method` never reaches an instance method or an
// attribute; an ungoverned declaration falls back to its own default.
// The single source of the rule for both `Npm` and `Npa` (#1255).
pub(crate) fn ruby_wrapped_is_public(
    keyword: RubyVisibilityCall,
    singleton: bool,
    body_flag: RubyVisibility,
) -> bool {
    if keyword.governs(singleton) {
        keyword.visibility == RubyVisibility::Public
    } else {
        ruby_declaration_is_public(singleton, body_flag)
    }
}

// The bare name a Ruby `Call` invokes on the enclosing class body.
//
// A receiver other than `self` puts the call on another object, so
// `Other.private :x` and `@cfg.attr_accessor :y` name nothing in *this*
// class and yield `None`. Both halves are read from the grammar's
// `receiver` / `method` fields rather than by scanning for the leading
// `identifier` (grammar-dispatch rule 3): the scan answers correctly
// only when the receiver happens to be a local variable, and reports
// the method name as the callee for a `constant`, an instance variable,
// a global, or a chained call — all of which are `_primary` receivers.
fn ruby_callee_name<'a>(call: &Node<'a>, source: &'a [u8]) -> Option<&'a str> {
    if let Some(receiver) = call.child_by_field_name("receiver")
        && receiver.kind_id() != Ruby::Zelf as u16
    {
        return None;
    }
    call.child_by_field_name("method")?.utf8_text(source)
}

// Whether a Ruby `Call` node invokes the `attr_*` macro family on the
// enclosing class. Each macro takes a list of attribute symbols and
// synthesises the matching reader / writer / accessor methods.
pub(crate) fn ruby_is_attr_macro(call: &Node, source: &[u8]) -> bool {
    matches!(
        ruby_callee_name(call, source),
        Some("attr_accessor" | "attr_reader" | "attr_writer")
    )
}

// Books `count` attributes against the class tallies, all of them
// public or none. Kept as a helper so the three declaration shapes
// `ruby_walk_class_body` recognises cannot disagree on the rule.
fn ruby_add_attributes(stats: &mut Stats, count: usize, public: bool) {
    stats.class_na += count;
    if public {
        stats.class_npa += count;
    }
}

// Attributes declared by an `attr_*` macro nested in a visibility call's
// argument list: `private attr_accessor :b` parses as a `private` call
// whose sole argument is the `attr_accessor` call, so the macro is never
// a direct child of the body (#1255).
fn ruby_wrapped_attr_count(call: &Node, source: &[u8]) -> usize {
    use Ruby::*;

    ruby_call_named_arguments(call)
        .filter(|arg| {
            matches!(arg.kind_id().into(), Call | Call2 | Call3 | Call4)
                && ruby_is_attr_macro(arg, source)
        })
        .map(|arg| ruby_attr_macro_symbol_count(&arg))
        .sum()
}

// Walks the direct children of a Ruby class / singleton-class body
// (`BodyStatement` under `Class` / `SingletonClass`) tallying:
// - class-scope assignments to `@var` (`InstanceVariable`) and
//   `@@var` (`ClassVariable`) — one attribute per assignment, regardless
//   of whether the RHS is a constant or another expression.
// - `attr_accessor` / `attr_reader` / `attr_writer` macros — one
//   attribute per symbol argument.
//
// Visibility flags follow Ruby's keyword-marker convention: a bare
// `private` / `public` / `protected` identifier flips the default for
// every subsequent declaration in the body. The default visibility at
// the top of every class body is `public`. The argument-form of those
// keywords (`private :foo`, `private attr_accessor :b`) does not flip
// the body-wide flag — matching Ruby's runtime behaviour — but it does
// govern the declarations it wraps, so an `attr_*` macro nested in a
// visibility call takes the keyword's visibility (#1255).
//
// The retroactive symbol form (`private :b` after `attr_accessor :b`)
// is deliberately not modelled here. It renames the visibility of the
// generated *reader* only, leaving `b=` public, so there is no single
// answer for the attribute — unlike `Npm`, where the symbol names one
// method (see #1399).
//
// Attribute assignments to instance/class variables are visible only
// via the methods that wrap them, so the visibility flag at the point
// of declaration is what `npa` should reflect.
pub(crate) fn ruby_walk_class_body(body: &Node, source: &[u8], stats: &mut Stats) {
    // bca: suppress(cognitive) — grammar dispatch match, clearest whole
    // One pass over the body carrying Ruby's body-wide visibility flag, with
    // an arm per attribute-declaring shape (`@x = …`, `attr_*` macros). The
    // flag must be readable at every arm, so the arms cannot be lifted out
    // without threading it back through each helper.
    use Ruby::*;

    let mut visibility = RubyVisibility::Public;
    for child in body.children() {
        if let Some(marker) = ruby_visibility_marker(&child, source) {
            visibility = marker;
            continue;
        }
        match child.kind_id().into() {
            Assignment | Assignment2 => {
                let Some(lhs) = child.children().next() else {
                    continue;
                };
                if matches!(lhs.kind_id().into(), InstanceVariable | ClassVariable) {
                    ruby_add_attributes(stats, 1, ruby_declaration_is_public(false, visibility));
                }
            }
            Call | Call2 | Call3 | Call4 if ruby_is_attr_macro(&child, source) => {
                ruby_add_attributes(
                    stats,
                    ruby_attr_macro_symbol_count(&child),
                    ruby_declaration_is_public(false, visibility),
                );
            }
            Call | Call2 | Call3 | Call4 => match ruby_visibility_effect(&child, source) {
                Some(RubyVisibilityEffect::Flag(flag)) => visibility = flag,
                // `private attr_accessor :b`. An attribute is always an
                // instance-level declaration, so a class-method keyword
                // never governs one: the macro it wraps still declares
                // its attributes, at the body-wide flag.
                Some(RubyVisibilityEffect::Arguments(keyword)) => {
                    ruby_add_attributes(
                        stats,
                        ruby_wrapped_attr_count(&child, source),
                        ruby_wrapped_is_public(keyword, false, visibility),
                    );
                }
                None => {}
            },
            _ => {}
        }
    }
}

// Go's lexical export rule: an identifier names an exported (public)
// member iff its first character is an uppercase Unicode letter
// (`unicode.IsUpper`). Uses `char::is_uppercase` on the first `char`
// (not `is_ascii_uppercase`) so non-ASCII exports such as `Ärger`
// are recognised. The blank identifier `_` and lowercase names are
// unexported. Shared by `Npa` (field names) and `Npm` (method
// names); see issue #458.
pub(crate) fn go_is_exported(name: &str) -> bool {
    name.chars().next().is_some_and(char::is_uppercase)
}

// Extracts the declared name of a Go struct field for visibility
// purposes. A named field carries one or more `FieldIdentifier`
// children; an embedded field has none and instead embeds a type,
// whose base identifier (`TypeIdentifier`) is the field's name —
// `io.Reader` embeds as `Reader`, `*Foo` as `Foo`. Returns the
// embedded type's name node so callers can read its text from
// `code`.
pub(crate) fn go_embedded_field_name<'a>(declaration: &Node<'a>) -> Option<Node<'a>> {
    use self::Go::{PointerType, QualifiedType, TypeIdentifier};

    declaration
        .children()
        .find_map(|child| match child.kind_id().into() {
            // `*Foo` (pointer embed) and `pkg.Foo` (qualified embed)
            // wrap the type; the base name is the final TypeIdentifier.
            PointerType | QualifiedType => child
                .children()
                .filter(|n| matches!(n.kind_id().into(), TypeIdentifier))
                .last(),
            TypeIdentifier => Some(child),
            _ => None,
        })
}

// Name-based for cross-grammar correctness (see `cpp_count_field_identifiers`).
//
// A `function_definition` child is accepted outright rather than
// recursed into: it *is* a function, whatever declarator it carries.
// Recursing would miss a conversion operator with an inline body,
// whose declarator is an `operator_cast` and not a
// `function_declarator` (#1258).
//
// A conversion operator declared *without* a body has no
// `function_definition` to accept either — it parses as a bare
// `declaration > operator_cast`, or `template_declaration >
// declaration > operator_cast` in the templated form, with only an
// `abstract_function_declarator` underneath. `operator_cast` is
// therefore matched in its own right; matching the
// `abstract_function_declarator` instead would also claim ordinary
// function-pointer type positions (#1298).
//
// A `function_declarator`, by contrast, is *not* accepted outright:
// the same node spells two opposite things, and only its `declarator`
// field tells them apart. A name (`realMethod()`) or an
// `operator_name` (`operator->()`) is a member function; a
// `parenthesized_declarator` may be either, so it is handed to
// `cpp_parenthesized_declares_function` (#1300).
//
// Neither `function_definition` nor `operator_cast` can be reached
// from the `Npa` call sites, which only ever pass a
// `field_declaration`: per both grammars' `node-types.json`, neither
// `field_declaration` nor the `pointer_declarator` /
// `reference_declarator` wrappers this recurses through can carry a
// `function_definition`, an `operator_cast`, or a `declaration` — so
// there is no path to either at any depth. Both are live only for
// `Npm`'s `declaration` / `template_declaration` children.
pub(crate) fn cpp_declares_function(node: &Node) -> bool {
    node.children().any(|child| match child.kind() {
        "function_declarator" => match child.child_by_field_name("declarator") {
            Some(inner) if inner.kind() == "parenthesized_declarator" => {
                cpp_parenthesized_declares_function(&inner)
            }
            _ => true,
        },
        "function_definition" | "operator_cast" => true,
        // Recurse through declarator wrappers that can sit above the
        // function_declarator (`Foo* operator->()`,
        // `template<...> T fn();`, constructor / destructor
        // `declaration`s inside a class body).
        "pointer_declarator" | "reference_declarator" | "declaration" => {
            cpp_declares_function(&child)
        }
        _ => false,
    })
}

// Given the `parenthesized_declarator` in a `function_declarator`'s
// `declarator` field, says whether the enclosing declaration is still
// a function. Parentheses alone do not make it a function *pointer* —
// what does is an indirection interposed inside them:
//
// - `void (f)();` and `int (operator+)(int);` are ordinary member
//   functions written with redundant parentheses. This is the
//   macro-defence idiom (`int (max)(int, int);`), which suppresses
//   expansion of a function-like macro of the same name.
// - `int (*fp)(int);` interposes a `*`, so the parameter list belongs
//   to the pointee and the member is data.
// - `int (*getFp(int))(int);` interposes a `*` too, but wraps a
//   further `function_declarator`, so it is a function *returning* a
//   function pointer. Neither language lets a function return a
//   function type, so that inner nesting is the only reading — the
//   same grammar fact `crate::c_declarator` relies on.
//
// Hence: every child must still read as a function. An indirection
// qualifies only when `cpp_declares_function` finds a function under
// it; a bare name, an `operator_name`, or an `ms_call_modifier`
// (`int (__cdecl *f)(int)`) never disqualifies on its own.
fn cpp_parenthesized_declares_function(paren: &Node) -> bool {
    paren.children().all(|child| match child.kind() {
        "pointer_declarator" | "reference_declarator" => cpp_declares_function(&child),
        "parenthesized_declarator" => cpp_parenthesized_declares_function(&child),
        _ => true,
    })
}

// Matches on node-kind NAMES, not one grammar's enum discriminants, so it
// is correct for every C-family grammar: upstream `Cpp` and the Mozilla
// `Mozcpp` fork (#720) assign *different* kind_ids to the same node kinds,
// and this helper is shared by both impls. (A `kind_id().into()` against a
// fixed `Cpp` enum silently miscounted Mozcpp nodes, zeroing its `npa`.)
// Each declarator's aliases all render to the one base name, so a single
// string arm covers the family.
pub(crate) fn cpp_count_field_identifiers(node: &Node) -> usize {
    let mut count = 0;
    for child in node.children() {
        match child.kind() {
            "field_identifier" => count += 1,
            // `function_declarator` / `parenthesized_declarator` are
            // in the set for the function-pointer data member
            // `int (*fp)(int);`, whose name is buried under both
            // (#1300). A member function's name cannot arrive through
            // them: reaching here means `cpp_declares_function`
            // declined the declaration, and it declines a
            // `function_declarator` only when an indirection inside
            // the parentheses makes the name the pointee's, never the
            // function's — `void (f)();` stays a function precisely so
            // that `f` is never counted here.
            "function_declarator"
            | "parenthesized_declarator"
            | "pointer_declarator"
            | "array_declarator"
            | "init_declarator"
            | "reference_declarator" => {
                count += cpp_count_field_identifiers(&child);
            }
            _ => {}
        }
    }
    count
}

// Returns `true` if `pat` contains exactly one `UNDERSCORE` token
// (identified by `underscore_id`) and no other named children.
// Anonymous tokens such as a leading `|` in a Rust or-pattern
// (`| _ => ...`) are skipped — they do not change the semantic
// meaning of the pattern.
//
// Shared between languages whose `default:`-equivalent wildcard
// pattern is a single `_`:
//   - Rust `match_pattern` (`Cyclomatic` and `Abc` for `RustCode`)
//   - Python `case_pattern` (`Abc` for `PythonCode`)
//
// The Rust caller passes its grammar's `UNDERSCORE` kind id; Python
// passes its own. Guard handling is the caller's responsibility —
// in Rust the guard is a sibling inside `match_pattern` and so adds
// a named child here (this helper returns `false`); in Python the
// guard is an `if_clause` sibling on the enclosing `case_clause`,
// so the caller must check the surrounding node separately.
pub(crate) fn pattern_is_bare_underscore(pat: &Node, underscore_id: u16) -> bool {
    let mut found_underscore = false;
    for child in pat.children() {
        if child.kind_id() == underscore_id {
            if found_underscore {
                return false;
            }
            found_underscore = true;
        } else if child.is_named() {
            return false;
        }
        // else: anonymous non-`_` token (like `|`) — skip.
    }
    found_underscore
}

// Returns `true` iff a Python `case_clause` should count as a
// non-trivial decision: either the pattern is not a bare `_`, or
// the clause carries an `if`-guard (`case _ if g:`).
//
// Shared between the `Cyclomatic` and `Abc` implementations for
// `PythonCode`. The bare wildcard without a guard is Python's
// `default:`-equivalent and is filtered out, matching Rust's bare-`_`
// MatchArm rule and Java/C#'s `default:` rule.
//
// `underscore_id` is the grammar's `Python::UNDERSCORE` kind id,
// passed in so the helper does not assume a particular module-path
// to the language enum.
pub(crate) fn python_case_clause_counts(node: &Node, underscore_id: u16) -> bool {
    let mut bare_underscore = false;
    for child in node.children() {
        match child.kind_id().into() {
            Python::IfClause => return true,
            Python::CasePattern => {
                bare_underscore = pattern_is_bare_underscore(&child, underscore_id);
                if !bare_underscore {
                    return true;
                }
            }
            _ => {}
        }
    }
    !bare_underscore
}

// Returns `true` iff a Ruby `in_clause` pattern-match arm (`case … in`)
// counts as a non-trivial decision: either its pattern is not a bare
// `_`, or the arm carries an `if` / `unless` guard. A bare wildcard
// `in _` with no guard is Ruby's `case … in` default arm and is
// filtered out, mirroring Rust's bare-`_` `MatchArm` rule and Python's
// `case _:` rule (#977).
//
// Shared between the `Cyclomatic` and `Abc` implementations for
// `RubyCode`. Ruby surfaces the wildcard as an `identifier` whose
// source text is `_` (there is no dedicated underscore token, unlike
// Rust / Python), so the pattern check reads the byte slice rather than
// matching a kind id. The pattern is the first *named* child — the `in`
// keyword token is anonymous and the guard / body follow the pattern.
pub(crate) fn ruby_in_clause_counts(in_clause: &Node, source: &[u8]) -> bool {
    let mut pattern: Option<Node> = None;
    for child in in_clause.children() {
        match child.kind_id().into() {
            // `_guard` is the hidden supertype; `if_guard` / `unless_guard`
            // are the concrete nodes the grammar emits (lesson #34).
            Ruby::Guard | Ruby::IfGuard | Ruby::UnlessGuard => return true,
            _ if pattern.is_none() && child.is_named() => pattern = Some(child),
            _ => {}
        }
    }
    pattern.is_none_or(|pat| {
        !(matches!(pat.kind_id().into(), Ruby::Identifier) && pat.utf8_text(source) == Some("_"))
    })
}

// A `visibility_modifier` node counts as public unless it has a direct
// `Zelf` child — the structural signature of `pub(self)` / `pub(in self)`,
// which restrict visibility to the current module (semantically private,
// issue #460). `pub(in self::inner)` nests `self` inside a
// `scoped_identifier` (not a direct child) and stays public. Shared by
// `rust_item_is_public` and the tuple-struct positional-field path so the
// two never drift.
pub(crate) fn rust_visibility_modifier_is_public(node: &Node) -> bool {
    !node.children().any(|c| c.kind_id() == Rust::Zelf)
}

// Returns true if `node` carries a `visibility_modifier` that makes the
// item public to its definition. Matches Rust's "public-only-when-`pub`"
// model: bare `pub`, `pub(crate)`, `pub(super)`, and `pub(in <path>)`
// widen visibility beyond the current module and count as public.
//
// The exceptions are `pub(self)` and `pub(in self)`, which restrict
// visibility to the current module — semantically identical to no
// modifier (private), so they must NOT count toward npm/npa (issue
// #460). The grammar emits the restriction keyword as a dedicated
// child of the `visibility_modifier`: `self` is a `Zelf` node, `crate`
// a `Crate` node, `super` a `Super` node. `pub(self)` and `pub(in self)`
// are exactly the forms whose `visibility_modifier` has a direct `Zelf`
// child; `pub(in self::inner)` nests `self` inside a `scoped_identifier`
// (not a direct child) and is treated as public, matching the issue's
// "only `self` / `in self` is private" scope. No source-text inspection
// is needed — the structural check is precise.
pub(crate) fn rust_item_is_public(node: &Node) -> bool {
    node.children()
        .filter(|c| c.kind_id() == Rust::VisibilityModifier)
        .any(|vis| rust_visibility_modifier_is_public(&vis))
}

// Kotlin's grammar models classes and interfaces under a single
// `class_declaration` node; the `class` / `interface` keyword child
// disambiguates. A `ClassBody` belongs to an interface iff its parent
// `class_declaration` has an `interface` keyword child.
pub(crate) fn kotlin_class_body_is_interface<'a>(
    class_body: &Node<'a>,
    ancestors: Ancestors<'a, '_>,
) -> bool {
    ancestors.parent(class_body).is_some_and(|p| {
        matches!(p.kind_id().into(), Kotlin::ClassDeclaration)
            && p.first_child(|id| id == Kotlin::Interface).is_some()
    })
}

// Kotlin's default visibility is `public`. A declaration is non-public
// only when it carries an explicit `private` / `protected` / `internal`
// modifier under its `Modifiers` child. Returns `true` for missing
// `Modifiers`, missing `VisibilityModifier`, or an explicit `public`
// modifier.
pub(crate) fn kotlin_is_public(decl: &Node) -> bool {
    let Some(modifiers) = decl.first_child(|id| id == Kotlin::Modifiers) else {
        return true;
    };
    let Some(visibility) = modifiers.first_child(|id| id == Kotlin::VisibilityModifier) else {
        return true;
    };
    // The visibility modifier holds exactly one keyword child; absence or
    // an explicit `public` both mean public.
    visibility
        .first_child(|id| {
            matches!(
                id.into(),
                Kotlin::Private | Kotlin::Protected | Kotlin::Internal
            )
        })
        .is_none()
}