Skip to main content

brokk_bifrost_cpp/
adapter.rs

1//! The C++ answers behind `CppAdapter`.
2//!
3//! `LanguageAdapter` is analysis-owned, so the trait impl itself stays in
4//! `analyzer/cpp/adapter.rs`; every answer it gives comes from here or from
5//! [`crate::test_detection`] and [`crate::queries`].
6
7use crate::declarations::{CppVisitor, collect_cpp_identifiers, recover_quoted_includes};
8use brokk_bifrost_core::analyzer::ProjectFile;
9use brokk_bifrost_core::analyzer::cognitive_complexity;
10use brokk_bifrost_core::analyzer::model::{Language, LanguageDialect};
11use brokk_bifrost_core::analyzer::parsed_file::ParsedFile;
12use brokk_bifrost_core::hash::HashMap;
13use std::sync::LazyLock;
14use tree_sitter::{Node, Tree};
15
16/// The file extension `CppAdapter` reports. `Language::Cpp` also covers `.c`,
17/// `.cc`, `.cxx` and the header spellings; this is only the canonical one.
18pub const CPP_FILE_EXTENSION: &str = "cpp";
19
20/// Tree-sitter node-kind mapping used by the cognitive-complexity scorer for
21/// C++. Node names are from the tree-sitter-cpp grammar.
22pub static CPP_COGNITIVE_CONFIG: LazyLock<cognitive_complexity::Config> =
23    LazyLock::new(|| cognitive_complexity::Config {
24        if_types: &["if_statement"],
25        loop_types: &["for_statement", "while_statement", "do_statement"],
26        catch_types: &["catch_clause"],
27        conditional_types: &["conditional_expression"],
28        case_types: &["case_statement"],
29        binary_types: &["binary_expression"],
30        logical_operators: &["&&", "||", "and", "or"],
31        jump_types: &["break_statement", "continue_statement"],
32        named_function_boundary_types: &["function_definition"],
33        anonymous_function_types: &["lambda_expression"],
34        else_clause_types: &["else_clause"],
35        default_case_predicate: Some(cpp_is_default_case),
36        ..cognitive_complexity::Config::empty()
37    });
38
39fn cpp_is_default_case(node: Node<'_>, _source: &str) -> bool {
40    node.child_by_field_name("value").is_none()
41}
42
43/// Extract `file` under the dialect its own path selects.
44pub fn parse_cpp_file(file: &ProjectFile, source: &str, tree: &Tree) -> ParsedFile {
45    parse_cpp_file_in_dialect(
46        file,
47        source,
48        tree,
49        LanguageDialect::for_path(Language::Cpp, file.rel_path()),
50    )
51}
52
53/// Extract `file` under an explicitly named dialect.
54///
55/// A header carries no compilation language of its own, so its blob has two
56/// legitimate readings: under [`LanguageDialect::CppC`] a tag declared inside
57/// an aggregate member list has file scope (C17 6.2.1), under the plain C++
58/// dialect it is a nested class. Milestone 3 of
59/// `.agents/plans/c-compilation-language-tag-scope.md` stores both readings of
60/// a header when they differ, so extraction has to be reachable under a
61/// dialect the path itself does not name.
62pub fn parse_cpp_file_in_dialect(
63    file: &ProjectFile,
64    source: &str,
65    tree: &Tree,
66    dialect: LanguageDialect,
67) -> ParsedFile {
68    let mut parsed = ParsedFile::new(String::new());
69    let root = tree.root_node();
70
71    collect_cpp_identifiers(root, source, &mut parsed.type_identifiers);
72
73    let mut visitor = CppVisitor {
74        file,
75        source,
76        parsed: &mut parsed,
77        c_tag_semantics: dialect == LanguageDialect::CppC,
78        recovered_class_sibling_scopes: HashMap::default(),
79        consumed_fragment_regions: Vec::new(),
80    };
81    visitor.visit_container(root, "", None, None, None, Vec::new());
82    recover_quoted_includes(source, &mut parsed);
83
84    parsed
85}
86
87/// Whether two readings of one blob disagree about any identity-bearing
88/// output: which declarations exist, what they are named, which are top level
89/// or definition-lookup entries, where they start and end, what they are
90/// nested in, and how they are signed.
91///
92/// This is the "differs" test behind storing a header's C projection only when
93/// it says something the C++ projection does not (issue #1970): absence of the
94/// second row-set must unambiguously mean "identical", so anything a
95/// resolution surface can observe has to be compared here.
96pub fn cpp_projections_differ(left: &ParsedFile, right: &ParsedFile) -> bool {
97    left.declarations() != right.declarations()
98        || left.top_level_declarations != right.top_level_declarations
99        || left.definition_lookup_units != right.definition_lookup_units
100        || left.children != right.children
101        || left.ranges != right.ranges
102        || left.signatures != right.signatures
103        || left.type_aliases != right.type_aliases
104}
105
106pub fn cpp_extract_call_receiver(reference: &str) -> Option<String> {
107    let trimmed = reference.trim();
108    let before_args = trimmed
109        .split_once('(')
110        .map(|(head, _)| head)
111        .unwrap_or(trimmed);
112    before_args
113        .rsplit_once("::")
114        .or_else(|| before_args.rsplit_once('.'))
115        .map(|(receiver, _)| receiver.to_string())
116}