big-code-analysis-ast 2.4.0

Parse and classification layer behind big-code-analysis (internal, version-locked)
Documentation
//! `Getter` implementation for MozC++.
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]

use super::*;
use crate::c_declarator::declarator_name;

// `GT2` is listed defensively, as in the Cpp getter.
const GENERIC_ANGLES: GenericAngleKinds = GenericAngleKinds {
    lists: &[
        Mozcpp::TemplateArgumentList as u16,
        Mozcpp::TemplateParameterList as u16,
    ],
    openers: &[Mozcpp::LT as u16],
    closers: &[Mozcpp::GT as u16, Mozcpp::GT2 as u16],
    is_misparse: never_misparsed,
};

impl Getter for MozcppCode {
    fn get_func_space_name<'a, 'tree>(
        node: &Node<'tree>,
        code: &'a [u8],
        _ancestors: Ancestors<'tree, '_>,
    ) -> Option<&'a str> {
        // Issue #285 contract: every `Mozcpp::FunctionDefinition*` alias
        // must be enumerated here AND in `get_space_kind` below AND
        // in `is_func` / `is_func_space` (see `src/checker.rs`).
        // The aliased kind_ids 489/491/494 are not emitted by the
        // currently pinned `tree-sitter-mozcpp` parse tables, so a
        // dropped variant would silently fall through to the
        // `_ => name-field` arm and yield the wrong name (or `None`).
        match node.kind_id().into() {
            Mozcpp::FunctionDefinition
            | Mozcpp::FunctionDefinition2
            | Mozcpp::FunctionDefinition3
            | Mozcpp::FunctionDefinition4 => {
                if let Some(op_cast) = node.first_child(|id| Mozcpp::OperatorCast == id) {
                    return node_text(code, &op_cast);
                }
                // The name is not a child of the function node — see
                // `crate::c_declarator` (#1208).
                if let Some(name) = declarator_name::<Self>(node)
                    && matches!(
                        name.kind_id().into(),
                        Mozcpp::TypeIdentifier
                            | Mozcpp::Identifier
                            | Mozcpp::FieldIdentifier
                            | Mozcpp::DestructorName
                            | Mozcpp::OperatorName
                            | Mozcpp::QualifiedIdentifier
                            | Mozcpp::QualifiedIdentifier2
                            | Mozcpp::QualifiedIdentifier3
                            | Mozcpp::QualifiedIdentifier4
                            | Mozcpp::TemplateFunction
                            | Mozcpp::TemplateMethod
                    )
                {
                    return node_text(code, &name);
                }
            }
            _ => {
                if let Some(name) = node.child_by_field_name("name") {
                    return node_text(code, &name);
                }
            }
        }
        None
    }

    fn get_space_kind(node: &Node) -> SpaceKind {
        use Mozcpp::*;

        // Issue #285 contract: keep every `FunctionDefinition*` alias
        // listed here — see the comment above `get_func_space_name`.
        match node.kind_id().into() {
            FunctionDefinition | FunctionDefinition2 | FunctionDefinition3
            | FunctionDefinition4 => SpaceKind::Function,
            // Bodiless specifiers open no space (#1555); see
            // `cpp_specifier_has_body`.
            StructSpecifier if crate::checker::cpp_specifier_has_body(node) => SpaceKind::Struct,
            ClassSpecifier if crate::checker::cpp_specifier_has_body(node) => SpaceKind::Class,
            NamespaceDefinition => SpaceKind::Namespace,
            TranslationUnit => SpaceKind::Unit,
            _ => SpaceKind::Unknown,
        }
    }

    fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> TokenRole {
        use Mozcpp::*;

        if GENERIC_ANGLES.is_closer(node, ancestors) {
            return TokenRole::Unknown;
        }

        // `LPAREN2` is a defensive arm (collapsed to `LPAREN` before
        // `kind_id()`; #768, see the Cpp note).
        match node.kind_id().into() {
            // Raw-string delimiter punctuation — the twin of the Cpp
            // arm, which carries the derivation (#1314). `.mozcpp` owns
            // no file extension, so nothing routes to it and it gets no
            // integration-snapshot coverage; the parity assertion in
            // `tests/parity/cpp_mozcpp_parity.rs` is what keeps this
            // clone from drifting.
            LPAREN
                if ancestors.parent_has_kind(node, RawStringLiteral as u16) =>
            {
                TokenRole::Unknown
            }
            DOT | DOTSTAR | LPAREN | LPAREN2 | COMMA | STAR | GTGT | COLON | SEMI | Return
            | Break | Continue | If | Else | Switch | Case | Default | For | While | Goto | Do
            | Delete | New | Try | Try2 | Catch | Throw | EQ | AMPAMP | PIPEPIPE | DASH
            | DASHDASH | DASHGT | DASHGTSTAR | PLUS | PLUSPLUS | SLASH | PERCENT | PIPE | AMP
            | LTLT | TILDE | LT | LTEQ | EQEQ | BANGEQ | GTEQ | GT | GT2 | LTEQGT | PLUSEQ
            | DASHEQ | BANG | STAREQ | SLASHEQ | PERCENTEQ | GTGTEQ | LTLTEQ | AMPEQ | CARET
            | CARETEQ | PIPEEQ | LBRACK | LBRACE | QMARK | COLONCOLON | PrimitiveType
            | TypeSpecifier | Sizeof
            // The ISO alternative tokens (`not`, `and`, `not_eq`, …) —
            // the derivation is on the Cpp twin (#1522).
            | Not | Compl | And | Or | Bitand | Bitor | Xor | NotEq | AndEq | OrEq | XorEq
            // A `sized_type_specifier` carries its `unsigned`/`signed`/`long`/
            // `short` modifiers as bare keyword tokens, not as `primitive_type`
            // children (`unsigned int` is `unsigned` + `primitive_type int`;
            // `signed long` and `long long` have no `primitive_type` at all).
            // Without these arms the modifiers fell into `Unknown` and were
            // dropped, so `unsigned int` collapsed to just `int` and a standalone
            // `signed long` contributed nothing to n1/N1 (issue #466). Each
            // modifier has a distinct kind_id, so keying by kind_id (the default
            // `operators` store) keeps them distinct in n1 while `long long`'s
            // two `long` tokens correctly fold to one n1 entry but two N1 hits.
            | Signed | Unsigned | Long | Short
            // An overloaded operator's name, the `operator` keyword and
            // `co_await` — the derivation is on the Cpp twin (#1296).
            | LPARENRPAREN | LBRACKRBRACK | DQUOTEDQUOTE | CoAwait | Operator
            // `co_return` / `co_yield`, billed as `return` is — the
            // derivation is on the Cpp twin (#1547).
            | CoReturn | CoYield
            // The type-declaration keywords — the derivation is on the
            // Cpp twin (#1552, #1557).
            | Class | Struct | Union | Enum | Namespace | Template | Typename | Typedef | Using
            // The declaration modifiers — the derivation is on the Cpp
            // twin (#1578). The Gecko `MOZ_*` annotation macros stay
            // unbilled, as attributes do.
            | Static | Extern | Register | Inline | Inline2 | Inline3 | Forceinline
            | ThreadLocal | Thread | Const | Constexpr | Volatile | Volatile2 | Restrict | Restrict2
            | Atomic | Noreturn | Noreturn2 | Nonnull | Alignas | Alignas2 | Extension
            | Virtual | Mutable | Constinit | Consteval | Explicit | Friend | Public | Private
            | Protected | Override | Final | Noexcept2
                => TokenRole::Operator,
            // `CharLiteral` — the full derivation lives on the same arm
            // in `src/getter/c.rs` (#1316): the wrapper is the only
            // classified node in a character literal, so it bills one
            // operand per literal, keyed by text, and `Checker::is_string`
            // deliberately stays without a `CharLiteral` arm.
            //
            // `This` — the operator-vs-operand derivation lives on the
            // same arm in `src/getter/cpp.rs` (#1361), where the fork's
            // upstream carries it. In short: `this` had been in neither
            // arm and so contributed nothing, and it is an operand
            // because it is the *receiver* half of `<recv> -> <field>`,
            // exactly where `p` stands in `p->x`. Mozcpp owns no file
            // extension, so nothing routes to it and no integration
            // snapshot reaches this clone — `cpp_and_mozcpp_agree_on_this`
            // in `tests/parity/cpp_mozcpp_parity.rs` is what keeps it
            // from drifting, the same guard the `RawStringLiteral`
            // `LPAREN` arm above relies on.
            Identifier | TypeIdentifier | FieldIdentifier | RawStringLiteral | StringLiteral
            | CharLiteral | NumberLiteral | True | False | Null | This | DOTDOTDOT => {
                TokenRole::Operand
            }
            // A namespace identifier is an operand only where it
            // *names* a namespace; the same kind also spells the
            // qualifier in `ns::thing`, which the final arm leaves
            // `Unknown` (#1096).
            NamespaceIdentifier if ancestors.parent_has_kind(node, NamespaceDefinition as u16) => {
                TokenRole::Operand
            }
            _ => TokenRole::Unknown,
        }
    }

    fn get_operator_spelling<'a>(
        node: &Node<'a>,
        ancestors: Ancestors<'a, '_>,
    ) -> Option<&'static str> {
        GENERIC_ANGLES.opener_spelling(node, ancestors)
    }

    // The overloaded operator names render as the operator they name;
    // the derivation is on the Cpp twin (#1296).
    fn get_operator_id_as_str(id: u16) -> &'static str {
        let typ = id.into();
        match typ {
            Mozcpp::LPAREN => "()",
            Mozcpp::LBRACK => "[]",
            Mozcpp::LBRACE => "{}",
            Mozcpp::LPARENRPAREN => "operator()",
            Mozcpp::LBRACKRBRACK => "operator[]",
            _ => typ.into(),
        }
    }
}