1use bonsai_common::{FileId, Span};
3use bonsai_lang_api::{
4 decl_index_with_handler, extract_imports_via,
5 kit::{
6 c_family_preproc_imports, collect_kinds, collect_param_type_aliases,
7 expression_operand_names_with_handler, first_named_child_of_kind, language_from_pack,
8 named_child_call_args_with_handler, node_text, parse_with, span_of, walk_flow_events,
9 },
10 AdapterContext, AdapterError, AggregateLayout, ArgumentPassingMode, CallKind, CallTargetExtraction,
11 DeclIndex, DeclKind, FieldWrite, FlowEvent, GrammarHandler, ImportIndex, ImportScope, ImportSpec,
12 LanguageAdapter, LanguageCapabilities, LanguageId, TypeAliasBinding, TypeAliasVocabulary, Visibility,
13 EMPTY_HANDLER,
14};
15use std::sync::OnceLock;
16use tree_sitter::{Language, Node, Tree};
17
18const CPP_TYPE_ALIASES: TypeAliasVocabulary = TypeAliasVocabulary {
29 fn_kinds: &["function_definition"],
30 param_kinds: &["parameter_declaration", "declaration"],
31 name_field: "declarator",
32 type_field: "type",
33};
34
35fn cpp_foreach_binding(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
36 (node.kind() == "for_range_loop")
37 .then(|| {
38 Some((
39 node.child_by_field_name("declarator")?,
40 node.child_by_field_name("right")?,
41 ))
42 })
43 .flatten()
44}
45
46pub const LANG_ID: LanguageId = LanguageId::new("cpp");
47const PACK_NAME: &str = "cpp";
48const CPP_CALL_KINDS: &[&str] = &["call_expression", "new_expression"];
49
50fn cpp_indirect_place_operand(node: Node<'_>) -> Option<Node<'_>> {
51 if node.kind() != "pointer_expression" {
52 return None;
53 }
54 let mut cursor = node.walk();
55 let has_indirection = node
56 .children(&mut cursor)
57 .any(|child| matches!(child.kind(), "*" | "&"));
58 has_indirection
59 .then(|| node.child_by_field_name("argument"))
60 .flatten()
61}
62
63fn cpp_call_target<'tree>(node: Node<'tree>, src: &[u8]) -> Option<CallTargetExtraction<'tree>> {
69 let target = match node.kind() {
70 "call_expression" => node.child_by_field_name("function")?,
71 "new_expression" => node.child_by_field_name("type")?,
72 _ => return None,
73 };
74 let full_text = cpp_call_target_without_template_arguments(target, src);
75 (!full_text.is_empty()).then_some(CallTargetExtraction {
76 node: target,
77 full_text,
78 })
79}
80
81fn cpp_call_target_without_template_arguments(target: Node<'_>, src: &[u8]) -> String {
82 let mut argument_ranges = Vec::new();
83 let mut stack = vec![target];
84 while let Some(node) = stack.pop() {
85 if node.kind() == "template_argument_list" {
86 argument_ranges.push(node.byte_range());
87 continue;
88 }
89 let mut cursor = node.walk();
90 stack.extend(node.named_children(&mut cursor));
91 }
92 if argument_ranges.is_empty() {
93 return node_text(&target, src).trim().to_string();
94 }
95 argument_ranges.sort_by_key(|range| (range.start, range.end));
96 let mut out = String::new();
97 let mut cursor = target.start_byte();
98 for range in argument_ranges {
99 if range.start > cursor {
100 out.push_str(std::str::from_utf8(&src[cursor..range.start]).unwrap_or_default());
101 }
102 cursor = cursor.max(range.end);
103 }
104 if cursor < target.end_byte() {
105 out.push_str(std::str::from_utf8(&src[cursor..target.end_byte()]).unwrap_or_default());
106 }
107 out.trim().to_string()
108}
109
110const HANDLER: GrammarHandler = GrammarHandler {
111 literal_value_kinds: &["null", "nullptr", "true", "false"],
112 string_literal_kinds: &[
113 "string_literal",
114 "raw_string_literal",
115 "char_literal",
116 "concatenated_string",
117 ],
118 comment_kinds: &["comment"],
119 doc_comment_prefixes: &["///", "//!", "/**"],
120 decorator_kinds: &["attribute"],
121 parameter_container_kinds: &["parameter_list"],
122 parameter_kinds: &["parameter_declaration", "optional_parameter_declaration"],
123 parameter_annotation_kinds: &["attribute"],
124 variadic_parameter_kinds: &["variadic_parameter", "variadic_declaration"],
125 binding_identifier_kinds: &["identifier"],
126 anonymous_variadic_token: Some("..."),
127 identifier_kinds: &["identifier"],
128 named_aggregate_kinds: &["initializer_list"],
129 positional_aggregate_kinds: &["initializer_list"],
130 aggregate_pair_kinds: &["initializer_pair"],
131 aggregate_key_field_names: &["designator"],
132 aggregate_value_field_names: &["value"],
133 static_field_name_kinds: &["field_identifier"],
134 aggregate_syntax_only_kinds: &["type_identifier"],
135 transparent_call_wrapper_kinds: &[
136 "field_expression",
137 "scoped_identifier",
138 "parenthesized_expression",
139 "await_expression",
140 "co_await_expression",
141 ],
142 single_expression_group_kinds: &["expression_list"],
143 assignment_target_wrapper_kinds: &[
144 "init_declarator",
145 "declarator",
146 "function_declarator",
147 "pointer_declarator",
148 "reference_declarator",
149 "parenthesized_declarator",
150 ],
151 binding_declaration_keyword_spellings: &["auto", "const"],
152 fn_kinds: &["function_definition"],
153 call_kinds: CPP_CALL_KINDS,
154 constructor_call_kinds: &["new_expression"],
155 call_callee_field_names: &["function"],
156 constructor_type_field_names: &["type"],
157 call_target_extractor: Some(cpp_call_target),
158 call_argument_field_names: &["arguments"],
159 call_argument_container_kinds: &["argument_list"],
160 writeback_operand_field_names: &["argument"],
161 indirect_place_operand_extractor: Some(cpp_indirect_place_operand),
162 lambda_body_field_names: &["body"],
163 pseudo_call_extractor: Some(extract_cpp_pseudo_call),
164 syntax_event_extractor: Some(extract_cpp_syntax_event),
165 argument_passing_mode_extractor: Some(cpp_argument_passing_mode),
166 expression_value_kind_extractor: Some(cpp_expression_value_kind),
167 call_ref_kinds: CPP_CALL_KINDS,
168 member_expression_kinds: &["field_expression", "qualified_identifier", "scoped_identifier"],
169 subscript_expression_kinds: &["subscript_expression"],
170 member_base_field_names: &["argument", "scope"],
171 member_name_field_names: &["field", "name"],
172 subscript_base_field_names: &["argument"],
173 subscript_index_field_names: &["index"],
174 syntax_error_tolerant_call_names: &["va_arg", "__builtin_va_arg"],
175 value_free_expression_kinds: &["sizeof_expression", "alignof_expression"],
176 class_kinds: &["class_specifier", "struct_specifier", "union_specifier"],
177 class_decl_kinds: &[
178 ("class_specifier", DeclKind::Class),
179 ("struct_specifier", DeclKind::Struct),
180 ("union_specifier", DeclKind::Struct),
181 ],
182 method_context_kinds: &["class_specifier", "struct_specifier", "union_specifier"],
183 if_kinds: &["if_statement", "conditional_expression", "switch_statement"],
184 branch_then_field_names: &["consequence", "body"],
185 branch_else_field_names: &["alternative"],
186 branch_condition_field_names: &["condition", "value"],
187 loop_body_field_names: &["body"],
188 loop_body_kinds: &["compound_statement", "expression_statement"],
189 branch_arm_kinds: &["compound_statement", "expression_statement"],
190 for_kinds: &["for_statement"],
191 foreach_kinds: &["for_range_loop"],
192 foreach_binding_extractor: Some(cpp_foreach_binding),
193 while_kinds: &["while_statement"],
194 do_kinds: &["do_statement"],
195 assignment_kinds: &["assignment_expression", "init_declarator"],
196 compound_assignment_operators: &["+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "&=", "^=", "|="],
197 positional_aggregate_assignment_kinds: &["init_declarator"],
198 positional_aggregate_value_kinds: &["initializer_list"],
199 return_kinds: &["return_statement", "co_return_statement"],
200 throw_kinds: &["throw_statement"],
201 lambda_kinds: &["lambda_expression"],
202 try_kinds: &["try_statement"],
203 catch_kinds: &["catch_clause"],
204 break_kinds: &["break_statement"],
205 continue_kinds: &["continue_statement"],
206 control_label_field_names: &[],
207 yield_kinds: &["co_yield_statement"],
208 yield_value_field_names: &["argument", "value"],
209 try_body_field_names: &["body"],
210 await_kinds: &["co_await_expression"],
211 constructor_names: bonsai_lang_api::NO_CONSTRUCTOR_METHOD_NAMES,
216 implicit_receiver_names: &["this"],
217 ..EMPTY_HANDLER
218};
219
220fn cpp_expression_value_kind(node: Node<'_>, _src: &[u8]) -> Option<bonsai_lang_api::AssignValueKind> {
221 matches!(
222 node.kind(),
223 "string_literal" | "char_literal" | "number_literal" | "true" | "false" | "nullptr"
224 )
225 .then_some(bonsai_lang_api::AssignValueKind::Literal)
226}
227
228fn cpp_argument_passing_mode(argument: Node<'_>, value: Node<'_>) -> ArgumentPassingMode {
229 if [argument, value].into_iter().any(|node| {
230 matches!(node.kind(), "pointer_expression" | "unary_expression") && {
231 let mut cursor = node.walk();
232 let has_address_of = node.children(&mut cursor).any(|child| child.kind() == "&");
233 has_address_of
234 }
235 }) {
236 ArgumentPassingMode::WriteBack
237 } else {
238 ArgumentPassingMode::Value
239 }
240}
241
242fn extract_cpp_pseudo_call(
243 node: Node<'_>,
244 file: FileId,
245 src: &[u8],
246 handler: &GrammarHandler,
247) -> Option<FlowEvent> {
248 if node.kind() != "delete_expression" {
249 return None;
250 }
251 Some(FlowEvent::Call {
252 span: span_of(file, &node),
253 receiver: None,
254 receiver_types: Vec::new(),
255 name: "delete".to_string(),
256 call_kind: CallKind::Operator,
257 args: named_child_call_args_with_handler(&node, file, src, handler),
258 })
259}
260
261fn extract_cpp_syntax_event(
267 node: Node<'_>,
268 file: FileId,
269 src: &[u8],
270 handler: &GrammarHandler,
271) -> Option<FlowEvent> {
272 let (name, value) = match node.kind() {
273 "init_declarator" => {
274 let value = node.child_by_field_name("value")?;
275 if value.kind() != "argument_list" {
276 return None;
277 }
278 let declaration = node.parent().filter(|parent| parent.kind() == "declaration")?;
279 let type_node = declaration.child_by_field_name("type")?;
280 (cpp_type_descriptor_name(&type_node, src)?, value)
281 }
282 "field_initializer" => {
288 let name_node = node.named_child(0)?;
289 let value = first_named_child_of_kind(&node, "argument_list")?;
290 (node_text(&name_node, src).trim().to_string(), value)
291 }
292 _ => return None,
293 };
294 if name.is_empty() {
295 return None;
296 }
297 Some(FlowEvent::Call {
298 span: span_of(file, &value),
299 receiver: None,
300 receiver_types: Vec::new(),
301 name,
302 call_kind: CallKind::Constructor,
303 args: named_child_call_args_with_handler(&value, file, src, handler),
304 })
305}
306
307#[derive(Debug, Default, Copy, Clone)]
309pub struct CppAdapter;
310
311impl CppAdapter {
312 #[must_use]
314 pub fn new() -> Self {
315 Self
316 }
317}
318
319fn cpp_tree_proves_language(tree: &Tree) -> bool {
320 static C_GRAMMAR: OnceLock<Option<Language>> = OnceLock::new();
321 let Some(c_grammar) = C_GRAMMAR.get_or_init(|| language_from_pack("c").ok()) else {
322 return false;
323 };
324 let mut stack = vec![tree.root_node()];
325 while let Some(node) = stack.pop() {
326 if node.is_named()
327 && !node.is_error()
328 && (!grammar_has_named_kind(c_grammar, node.kind()) || is_cpp_braced_construction(node))
329 {
330 return true;
331 }
332 let mut cursor = node.walk();
333 stack.extend(node.named_children(&mut cursor));
334 }
335 false
336}
337
338fn grammar_has_named_kind(grammar: &Language, kind: &str) -> bool {
339 let id = grammar.id_for_node_kind(kind, true);
340 id != 0 && grammar.node_kind_is_named(id) && grammar.node_kind_for_id(id) == Some(kind)
341}
342
343fn is_cpp_braced_construction(node: Node<'_>) -> bool {
348 node.kind() == "compound_literal_expression"
349 && node
350 .child_by_field_name("type")
351 .is_some_and(|ty| ty.start_byte() == node.start_byte())
352}
353
354impl LanguageAdapter for CppAdapter {
355 fn language_id(&self) -> LanguageId {
356 LANG_ID
357 }
358 fn display_name(&self) -> &'static str {
359 "C++"
360 }
361 fn file_extensions(&self) -> &'static [&'static str] {
362 &["cpp", "cc", "cxx", "hpp", "hh", "hxx", "h"]
366 }
367 fn tree_sitter_language(&self) -> Result<Language, AdapterError> {
368 language_from_pack(PACK_NAME)
369 }
370 fn source_syntax_proves_language(
371 &self,
372 _snapshot: &bonsai_lang_api::FileSnapshot,
373 tree: &Tree,
374 ) -> bonsai_lang_api::LanguageOwnershipEvidence {
375 if cpp_tree_proves_language(tree) {
376 bonsai_lang_api::LanguageOwnershipEvidence::Proven
377 } else {
378 bonsai_lang_api::LanguageOwnershipEvidence::Excluded
379 }
380 }
381 fn parse_recovery_edits(
382 &self,
383 snapshot: &bonsai_lang_api::FileSnapshot,
384 vfs: &bonsai_lang_api::Vfs,
385 tree: &Tree,
386 ) -> Vec<bonsai_lang_api::ParseRecoveryEdit> {
387 let mut edits = bonsai_lang_api::branch_free_conditional_recovery_edits(
388 snapshot,
389 tree,
390 bonsai_lang_api::ConditionalDirectiveSyntax {
391 openings_with_condition: &["#if", "#ifdef", "#ifndef"],
392 alternatives_with_condition: &["#elif", "#elifdef", "#elifndef"],
393 alternatives_without_condition: &["#else"],
394 closing: "#endif",
395 trailing_comment_prefixes: &["//", "/*"],
396 },
397 );
398 edits.extend(bonsai_lang_api::c_family_declaration_macro_recovery_edits(
399 snapshot,
400 vfs,
401 tree,
402 &["va_arg", "__builtin_va_arg"],
403 ));
404 edits
405 }
406 fn capabilities(&self) -> LanguageCapabilities {
407 LanguageCapabilities {
412 macros: bonsai_lang_api::CapabilityLevel::Partial,
413 receiver_types: bonsai_lang_api::CapabilityLevel::Partial,
414 module_default_export_names: &[],
415 universal_type_names: &[],
416 module_path_syntax: bonsai_lang_api::ModulePathSyntax {
417 rooted_prefixes: &["::"],
418 repeatable_rooted_prefixes: &[],
419 },
420 constructor_method_names: bonsai_lang_api::NO_CONSTRUCTOR_METHOD_NAMES,
423 super_receiver_tokens: &[],
424 implicit_receiver_tokens: &["this"],
425 same_directory_unqualified_calls: true,
426 build_target_linkage: true,
427 ..LanguageCapabilities::partial_baseline()
428 }
429 }
430 fn extract_declarations(&self, file: FileId, ctx: &AdapterContext<'_>) -> DeclIndex {
431 let mut decl_index = decl_index_with_handler(PACK_NAME, file, ctx, &HANDLER);
432 mark_cpp_constructors(&mut decl_index);
433 bonsai_lang_api::apply_file_stem_semantic_identity(&mut decl_index, ctx);
442 let private_function_names = collect_tu_private_function_names(file, ctx);
443 for decl in &mut decl_index.defs {
444 if private_function_names.contains(&decl.name) {
445 decl.visibility = Visibility::Private;
446 }
447 }
448 if let Some((snapshot, tree)) = parse_with(PACK_NAME, file, ctx) {
455 let src = snapshot.text.as_bytes();
456 bonsai_lang_api::populate_decl_return_types(&mut decl_index, &tree, src, &HANDLER);
459 let bases_by_span = collect_cpp_class_bases(&tree, file, src);
460 let fields_by_class = collect_cpp_class_fields(&tree, file, src);
461 decl_index.aggregate_layouts = fields_by_class
462 .iter()
463 .map(|(_, type_name, fields)| AggregateLayout {
464 type_name: type_name.clone(),
465 fields: fields.clone(),
466 })
467 .collect();
468 let fields_by_parent = cpp_fields_by_parent_symbol(&decl_index, &fields_by_class);
469 let access_by_span = collect_cpp_member_visibility(&tree, file, src);
470 let alias_map = collect_param_type_aliases(&tree, file, src, &CPP_TYPE_ALIASES);
471 let cast_aliases = collect_cpp_cast_aliases(&tree, file, src).into_iter().fold(
475 std::collections::HashMap::<Span, Vec<TypeAliasBinding>>::new(),
476 |mut by_span, (span, binding)| {
477 by_span.entry(span).or_default().push(binding);
478 by_span
479 },
480 );
481 let initializer_specs = collect_cpp_initializer_field_specs(&tree, file, src)
482 .into_iter()
483 .collect::<std::collections::HashMap<_, _>>();
484 let initializer_events = collect_cpp_constructor_initializer_events(&tree, file, src)
485 .into_iter()
486 .collect::<std::collections::HashMap<_, _>>();
487 for decl in &mut decl_index.defs {
488 if let Some(events) = initializer_events.get(&decl.span) {
489 let mut ordered = events.clone();
490 ordered.append(&mut decl.flow_events);
491 decl.flow_events = ordered;
492 }
493 if let Some(fields) = decl.parent.and_then(|parent| fields_by_parent.get(&parent)) {
494 bonsai_lang_api::qualify_receiver_field_expression_flows(
495 &mut decl.flow_events,
496 fields,
497 "this",
498 );
499 }
500 if let Some(visibility) = access_by_span.get(&decl.span).copied() {
501 decl.visibility = visibility;
502 }
503 if let Some(aliases) = alias_map.get(&decl.span) {
504 decl.type_aliases = aliases.clone();
505 }
506 if let Some(bindings) = cast_aliases.get(&decl.span) {
507 decl.type_aliases.extend(bindings.iter().cloned());
508 }
509 collapse_cpp_same_type_copy_initializers(&mut decl.flow_events, &decl.type_aliases);
510 fix_cpp_catch_params(&mut decl.flow_events, &tree, src);
515 if let Some(specs) = initializer_specs.get(&decl.span) {
518 for spec in specs {
519 let source_param_indices = decl
520 .params
521 .iter()
522 .enumerate()
523 .filter_map(|(idx, param)| {
524 spec.sources
525 .iter()
526 .any(|source| cpp_source_mentions_param(source, param))
527 .then_some(idx)
528 })
529 .collect::<Vec<_>>();
530 if source_param_indices.is_empty() {
531 continue;
532 }
533 decl.receiver_field_writes.push(FieldWrite {
534 span: spec.span,
535 target: format!("this.{}", spec.field),
536 source_param_indices,
537 });
538 }
539 decl.receiver_field_writes
540 .sort_by_key(|write| (write.span.start, write.target.clone()));
541 decl.receiver_field_writes.dedup_by(|a, b| {
542 a.span == b.span
543 && a.target == b.target
544 && a.source_param_indices == b.source_param_indices
545 });
546 }
547 if !is_class_like(decl.kind) {
548 continue;
549 }
550 if let Some(bases) = bases_by_span.iter().find_map(|(span, name, bases)| {
551 (*span == decl.span || name == &decl.name).then_some(bases)
552 }) {
553 decl.bases = bases.clone();
554 }
555 }
556 }
557 for decl in &mut decl_index.defs {
558 bonsai_lang_api::normalize_call_result_assignment_sources(&mut decl.flow_events);
559 let has_variadic_param = decl
560 .params
561 .iter()
562 .any(|param| param == bonsai_lang_api::kit::SYNTHETIC_VARARGS_PARAM);
563 bonsai_lang_api::kit::normalize_variadic_builtin_flow(
564 &mut decl.flow_events,
565 has_variadic_param,
566 &["va_start", "__builtin_va_start"],
567 &["va_arg", "__builtin_va_arg"],
568 );
569 apply_cpp_moved_argument_places(&mut decl.flow_events);
570 }
571 bonsai_lang_api::apply_constructor_result_type_aliases(&mut decl_index);
579 bonsai_lang_api::apply_class_field_type_aliases(&mut decl_index);
580 decl_index
581 }
582 fn extract_imports(&self, file: FileId, ctx: &AdapterContext<'_>) -> ImportIndex {
583 extract_imports_via(PACK_NAME, file, ctx, parse_imports)
584 }
585}
586
587fn collapse_cpp_same_type_copy_initializers(events: &mut Vec<FlowEvent>, aliases: &[TypeAliasBinding]) {
594 for event in events.iter_mut() {
595 match event {
596 FlowEvent::Branch {
597 then_events,
598 else_events,
599 ..
600 } => {
601 collapse_cpp_same_type_copy_initializers(then_events, aliases);
602 collapse_cpp_same_type_copy_initializers(else_events, aliases);
603 }
604 FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
605 collapse_cpp_same_type_copy_initializers(body, aliases);
606 }
607 FlowEvent::Try {
608 body,
609 catch_events,
610 finally_events,
611 ..
612 } => {
613 collapse_cpp_same_type_copy_initializers(body, aliases);
614 collapse_cpp_same_type_copy_initializers(catch_events, aliases);
615 collapse_cpp_same_type_copy_initializers(finally_events, aliases);
616 }
617 _ => {}
618 }
619 }
620 events.retain(|event| {
621 let FlowEvent::AggregateAssign {
622 target,
623 type_name,
624 value_flow,
625 ..
626 } = event
627 else {
628 return true;
629 };
630 if !value_flow.aggregate_fields.is_empty()
631 || !value_flow.spreads.is_empty()
632 || value_flow.tuple_items.len() != 1
633 {
634 return true;
635 }
636 let Some(source) = value_flow.tuple_items[0].place.as_deref() else {
637 return true;
638 };
639 let declared_type = type_name.as_deref().or_else(|| {
640 aliases
641 .iter()
642 .find(|alias| alias.name == *target)
643 .map(|alias| alias.type_name.as_str())
644 });
645 let source_type = aliases
646 .iter()
647 .find(|alias| alias.name == source)
648 .map(|alias| alias.type_name.as_str());
649 let (Some(declared_type), Some(source_type)) = (declared_type, source_type) else {
650 return true;
651 };
652 bonsai_lang_api::kit::canonical_simple_type_name(declared_type)
653 != bonsai_lang_api::kit::canonical_simple_type_name(source_type)
654 });
655}
656
657fn collect_cpp_constructor_initializer_events(
658 tree: &Tree,
659 file: FileId,
660 src: &[u8],
661) -> Vec<(Span, Vec<FlowEvent>)> {
662 let mut out = Vec::new();
663 for function in collect_kinds(tree, &["function_definition"]) {
664 let Some(initializers) = first_named_child_of_kind(&function, "field_initializer_list") else {
665 continue;
666 };
667 let events = walk_flow_events(initializers, file, src, &HANDLER, &[]);
668 if !events.is_empty() {
669 out.push((span_of(file, &function), events));
670 }
671 }
672 out
673}
674
675fn apply_cpp_moved_argument_places(events: &mut [FlowEvent]) {
682 let mut moved = Vec::new();
683 collect_cpp_moved_events(events, &mut moved);
684 apply_cpp_moved_argument_places_with_events(events, &moved);
685}
686
687fn collect_cpp_moved_events(events: &[FlowEvent], out: &mut Vec<(Span, String)>) {
688 for event in events {
689 match event {
690 FlowEvent::Lifecycle {
691 span,
692 name,
693 transition,
694 } if transition == "moved" && !name.is_empty() => out.push((*span, name.clone())),
695 FlowEvent::Branch {
696 then_events,
697 else_events,
698 ..
699 } => {
700 collect_cpp_moved_events(then_events, out);
701 collect_cpp_moved_events(else_events, out);
702 }
703 FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
704 collect_cpp_moved_events(body, out);
705 }
706 FlowEvent::Try {
707 body,
708 catch_events,
709 finally_events,
710 ..
711 } => {
712 collect_cpp_moved_events(body, out);
713 collect_cpp_moved_events(catch_events, out);
714 collect_cpp_moved_events(finally_events, out);
715 }
716 _ => {}
717 }
718 }
719}
720
721fn apply_cpp_moved_argument_places_with_events(events: &mut [FlowEvent], moved: &[(Span, String)]) {
722 for event in events {
723 match event {
724 FlowEvent::Call { args, .. } => {
725 for arg in args {
726 if arg.place.is_some() {
727 continue;
728 }
729 let candidate = moved.iter().find_map(|(span, name)| {
730 (arg.span.file == span.file
731 && arg.span.start <= span.start
732 && span.end <= arg.span.end
733 && arg.source_names.iter().any(|source| source == name))
734 .then_some(name)
735 });
736 if let Some(candidate) = candidate {
737 arg.place = Some(candidate.clone());
738 }
739 }
740 }
741 FlowEvent::Branch {
742 then_events,
743 else_events,
744 ..
745 } => {
746 apply_cpp_moved_argument_places_with_events(then_events, moved);
747 apply_cpp_moved_argument_places_with_events(else_events, moved);
748 }
749 FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
750 apply_cpp_moved_argument_places_with_events(body, moved);
751 }
752 FlowEvent::Try {
753 body,
754 catch_events,
755 finally_events,
756 ..
757 } => {
758 apply_cpp_moved_argument_places_with_events(body, moved);
759 apply_cpp_moved_argument_places_with_events(catch_events, moved);
760 apply_cpp_moved_argument_places_with_events(finally_events, moved);
761 }
762 _ => {}
763 }
764 }
765}
766
767fn mark_cpp_constructors(decl_index: &mut DeclIndex) {
772 let class_names = decl_index
773 .defs
774 .iter()
775 .filter(|decl| is_class_like(decl.kind))
776 .map(|decl| (decl.symbol, decl.name.clone()))
777 .collect::<std::collections::HashMap<_, _>>();
778 for decl in &mut decl_index.defs {
779 if !matches!(decl.kind, DeclKind::Function | DeclKind::Method) {
780 continue;
781 }
782 let Some(parent_name) = decl.parent.and_then(|parent| class_names.get(&parent)) else {
783 continue;
784 };
785 if decl.name == *parent_name {
786 decl.kind = DeclKind::Constructor;
787 if decl.implicit_receiver_names.is_empty() {
788 decl.implicit_receiver_names.push("this".to_string());
789 }
790 }
791 }
792}
793
794fn collect_cpp_class_fields(tree: &Tree, file: FileId, src: &[u8]) -> Vec<(Span, String, Vec<String>)> {
795 let mut out = Vec::new();
796 for class_node in collect_kinds(tree, &["class_specifier", "struct_specifier", "union_specifier"]) {
797 let Some(name_node) = class_node
798 .child_by_field_name("name")
799 .or_else(|| first_named_child_of_kind(&class_node, "type_identifier"))
800 else {
801 continue;
802 };
803 let Some(body) = class_node.child_by_field_name("body") else {
804 continue;
805 };
806 let mut fields = Vec::new();
807 let mut body_cursor = body.walk();
808 for field_decl in body
809 .named_children(&mut body_cursor)
810 .filter(|child| child.kind() == "field_declaration")
811 {
812 for child_index in 0..field_decl.child_count() {
813 if field_decl.field_name_for_child(child_index as u32) != Some("declarator") {
814 continue;
815 }
816 let Some(child) = field_decl.child(child_index as u32) else {
817 continue;
818 };
819 if !child.is_named() || cpp_declarator_is_function(child) {
820 continue;
821 }
822 if let Some(identifier) = cpp_binding_identifier(child) {
823 let name = node_text(&identifier, src).trim();
824 if !name.is_empty() && !fields.iter().any(|field| field == name) {
825 fields.push(name.to_string());
826 }
827 }
828 }
829 }
830 if !fields.is_empty() {
831 out.push((
832 span_of(file, &class_node),
833 node_text(&name_node, src).trim().to_string(),
834 fields,
835 ));
836 }
837 }
838 out
839}
840
841fn cpp_declarator_is_function(node: Node<'_>) -> bool {
842 let mut stack = vec![node];
843 while let Some(current) = stack.pop() {
844 if current.kind() == "function_declarator" {
845 return true;
846 }
847 let mut cursor = current.walk();
848 stack.extend(current.named_children(&mut cursor));
849 }
850 false
851}
852
853fn cpp_binding_identifier(node: Node<'_>) -> Option<Node<'_>> {
854 if matches!(node.kind(), "identifier" | "field_identifier") {
855 return Some(node);
856 }
857 for field in ["declarator", "name"] {
858 if let Some(child) = node.child_by_field_name(field) {
859 if let Some(identifier) = cpp_binding_identifier(child) {
860 return Some(identifier);
861 }
862 }
863 }
864 let mut cursor = node.walk();
865 for child in node.named_children(&mut cursor) {
866 if let Some(identifier) = cpp_binding_identifier(child) {
867 return Some(identifier);
868 }
869 }
870 None
871}
872
873fn cpp_fields_by_parent_symbol(
874 index: &DeclIndex,
875 fields_by_class: &[(Span, String, Vec<String>)],
876) -> std::collections::HashMap<bonsai_common::SymbolId, std::collections::HashSet<String>> {
877 index
878 .defs
879 .iter()
880 .filter(|decl| is_class_like(decl.kind))
881 .filter_map(|decl| {
882 fields_by_class
883 .iter()
884 .find(|(span, name, _)| *span == decl.span || *name == decl.name)
885 .map(|(_, _, fields)| (decl.symbol, fields.iter().cloned().collect()))
886 })
887 .collect()
888}
889
890fn collect_tu_private_function_names(
896 file: FileId,
897 ctx: &AdapterContext<'_>,
898) -> std::collections::HashSet<String> {
899 let mut private_names: std::collections::HashSet<String> = std::collections::HashSet::new();
900 let Some((snapshot, tree)) = parse_with(PACK_NAME, file, ctx) else {
902 return private_names;
903 };
904 let src = snapshot.text.as_bytes();
905 let root = tree.root_node();
906 walk_for_tu_private(root, src, false, &mut private_names);
907 private_names
908}
909
910#[derive(Clone, Debug)]
911struct CppInitializerFieldSpec {
912 span: Span,
913 field: String,
914 sources: Vec<String>,
915}
916
917fn collect_cpp_initializer_field_specs(
918 tree: &Tree,
919 file: FileId,
920 src: &[u8],
921) -> Vec<(Span, Vec<CppInitializerFieldSpec>)> {
922 let mut out = Vec::new();
923 for fn_node in collect_kinds(tree, &["function_definition"]) {
924 let Some(initializers) = first_named_child_of_kind(&fn_node, "field_initializer_list") else {
925 continue;
926 };
927 let mut specs = Vec::new();
928 let mut cursor = initializers.walk();
929 for init in initializers.named_children(&mut cursor) {
930 if init.kind() != "field_initializer" {
931 continue;
932 }
933 let Some(field_node) = first_named_child_of_kind(&init, "field_identifier") else {
934 continue;
935 };
936 let Some(value_node) = first_named_child_of_kind(&init, "argument_list") else {
937 continue;
938 };
939 let field = node_text(&field_node, src).trim().to_string();
940 let sources = expression_operand_names_with_handler(&value_node, src, &HANDLER);
941 if field.is_empty() || sources.is_empty() {
942 continue;
943 }
944 specs.push(CppInitializerFieldSpec {
945 span: span_of(file, &init),
946 field,
947 sources,
948 });
949 }
950 if !specs.is_empty() {
951 out.push((span_of(file, &fn_node), specs));
952 }
953 }
954 out
955}
956
957fn cpp_source_mentions_param(source: &str, param: &str) -> bool {
958 let source = bonsai_common::normalize_qualified_name(source);
959 let param = bonsai_common::normalize_qualified_name(param);
960 source == param
961 || source
962 .strip_prefix(¶m)
963 .is_some_and(|projection| projection.starts_with('.'))
964}
965
966fn collect_cpp_member_visibility(
967 tree: &Tree,
968 file: FileId,
969 src: &[u8],
970) -> std::collections::HashMap<Span, Visibility> {
971 let mut out = std::collections::HashMap::new();
972 for class_node in collect_kinds(tree, &["class_specifier", "struct_specifier"]) {
973 let default_visibility = if class_node.kind() == "struct_specifier" {
974 Visibility::Public
975 } else {
976 Visibility::Private
977 };
978 let mut current_visibility = default_visibility;
979 if let Some(body) = class_node.child_by_field_name("body") {
980 let mut cursor = body.walk();
981 for child in body.named_children(&mut cursor) {
982 if child.kind() == "access_specifier" {
983 current_visibility = cpp_access_visibility(node_text(&child, src), default_visibility);
984 continue;
985 }
986 if child.kind() == "function_definition" {
987 out.insert(span_of(file, &child), current_visibility);
988 }
989 }
990 }
991 }
992 out
993}
994
995fn cpp_access_visibility(raw: &str, default_visibility: Visibility) -> Visibility {
996 match raw.trim().trim_end_matches(':') {
997 "public" => Visibility::Public,
998 "protected" => Visibility::Protected,
999 "private" => Visibility::Private,
1000 _ => default_visibility,
1001 }
1002}
1003
1004fn walk_for_tu_private(
1008 root: Node<'_>,
1009 src: &[u8],
1010 inside_anonymous_ns: bool,
1011 private_names: &mut std::collections::HashSet<String>,
1012) {
1013 let mut stack = vec![(root, inside_anonymous_ns)];
1014 while let Some((node, inside_anonymous_ns)) = stack.pop() {
1015 if node.kind() == "function_definition"
1016 && (inside_anonymous_ns || function_has_static_specifier(&node, src))
1017 {
1018 if let Some(name) = function_name(&node, src) {
1019 private_names.insert(name);
1020 }
1021 }
1022 let mut cursor = node.walk();
1023 for child in node.children(&mut cursor) {
1024 let entering_anonymous = inside_anonymous_ns
1027 || (child.kind() == "namespace_definition" && !namespace_is_named(&child));
1028 stack.push((child, entering_anonymous));
1029 }
1030 }
1031}
1032
1033fn namespace_is_named(node: &Node<'_>) -> bool {
1036 if node.child_by_field_name("name").is_some() {
1037 return true;
1038 }
1039 let mut cursor = node.walk();
1040 let has_identifier = node
1041 .children(&mut cursor)
1042 .any(|child| child.kind() == "namespace_identifier" || child.kind() == "identifier");
1043 has_identifier
1044}
1045
1046fn function_has_static_specifier(node: &Node<'_>, src: &[u8]) -> bool {
1049 let mut cursor = node.walk();
1050 for child in node.children(&mut cursor) {
1051 if child.kind() == "storage_class_specifier" && node_text(&child, src) == "static" {
1052 return true;
1053 }
1054 }
1055 false
1056}
1057
1058fn function_name(node: &Node<'_>, src: &[u8]) -> Option<String> {
1061 let declarator = node.child_by_field_name("declarator")?;
1062 extract_function_identifier(&declarator, src)
1063}
1064
1065fn extract_function_identifier(node: &Node<'_>, src: &[u8]) -> Option<String> {
1069 if matches!(
1070 node.kind(),
1071 "identifier" | "field_identifier" | "destructor_name" | "operator_name"
1072 ) {
1073 return Some(node_text(node, src).to_string());
1074 }
1075 let mut cursor = node.walk();
1076 for child in node.children(&mut cursor) {
1077 if let Some(found) = extract_function_identifier(&child, src) {
1078 return Some(found);
1079 }
1080 }
1081 None
1082}
1083
1084fn is_class_like(kind: DeclKind) -> bool {
1087 matches!(
1088 kind,
1089 DeclKind::Class | DeclKind::Interface | DeclKind::Trait | DeclKind::Struct | DeclKind::Enum
1090 )
1091}
1092
1093fn collect_cpp_class_bases(
1107 tree: &Tree,
1108 file: FileId,
1109 src: &[u8],
1110) -> Vec<(bonsai_common::Span, String, Vec<String>)> {
1111 let mut bases_by_class = Vec::new();
1112 let class_kinds = &["class_specifier", "struct_specifier", "union_specifier"];
1113 for class_node in collect_kinds(tree, class_kinds) {
1114 let Some(name_node) = class_node
1115 .child_by_field_name("name")
1116 .or_else(|| first_named_child_of_kind(&class_node, "type_identifier"))
1117 .or_else(|| first_named_child_of_kind(&class_node, "identifier"))
1118 else {
1119 continue;
1120 };
1121 let class_name = node_text(&name_node, src).trim();
1122 if class_name.is_empty() {
1123 continue;
1124 }
1125 let mut bases: Vec<String> = Vec::new();
1126 let mut class_cursor = class_node.walk();
1127 for class_child in class_node.named_children(&mut class_cursor) {
1128 if class_child.kind() != "base_class_clause" {
1132 continue;
1133 }
1134 let mut clause_cursor = class_child.walk();
1135 for clause_child in class_child.named_children(&mut clause_cursor) {
1136 match clause_child.kind() {
1137 "type_identifier"
1138 | "qualified_identifier"
1139 | "template_type"
1140 | "scoped_type_identifier" => {
1141 if let Some(name) = canonical_cpp_base_name(node_text(&clause_child, src)) {
1142 if !bases.iter().any(|existing| existing == &name) {
1144 bases.push(name);
1145 }
1146 }
1147 }
1148 _ => {}
1149 }
1150 }
1151 }
1152 if !bases.is_empty() {
1153 bases_by_class.push((span_of(file, &class_node), class_name.to_string(), bases));
1154 }
1155 }
1156 bases_by_class
1157}
1158
1159fn collect_cpp_cast_aliases(
1170 tree: &Tree,
1171 file: FileId,
1172 src: &[u8],
1173) -> Vec<(bonsai_common::Span, TypeAliasBinding)> {
1174 let mut out = Vec::new();
1175 for decl_node in collect_kinds(tree, &["declaration"]) {
1176 let Some(type_node) = decl_node.child_by_field_name("type") else {
1177 continue;
1178 };
1179 if type_node.kind() != "placeholder_type_specifier" {
1180 continue;
1181 }
1182 let Some(init) = first_named_child_of_kind(&decl_node, "init_declarator") else {
1183 continue;
1184 };
1185 let Some(decl_field) = init.child_by_field_name("declarator") else {
1186 continue;
1187 };
1188 let name_node = if decl_field.kind() == "identifier" {
1189 decl_field
1190 } else {
1191 match cpp_first_descendant_of_kind(&decl_field, "identifier") {
1192 Some(n) => n,
1193 None => continue,
1194 }
1195 };
1196 let name = node_text(&name_node, src).trim().to_string();
1197 if name.is_empty() {
1198 continue;
1199 }
1200 let Some(value) = init.child_by_field_name("value") else {
1201 continue;
1202 };
1203 let Some(type_name) = cpp_cast_type_of_value(&value, src) else {
1204 continue;
1205 };
1206 let Some(fn_span) = cpp_enclosing_fn_span(&decl_node, file) else {
1207 continue;
1208 };
1209 out.push((fn_span, TypeAliasBinding { name, type_name }));
1210 }
1211 out
1212}
1213
1214fn cpp_cast_type_of_value(value: &Node<'_>, src: &[u8]) -> Option<String> {
1219 match value.kind() {
1220 "cast_expression" => {
1221 let type_node = value.child_by_field_name("type")?;
1222 cpp_type_descriptor_name(&type_node, src)
1223 }
1224 "call_expression" => {
1225 let func = value.child_by_field_name("function")?;
1226 if func.kind() != "template_function" {
1227 return None;
1228 }
1229 let name = func.child_by_field_name("name")?;
1230 if !matches!(
1231 node_text(&name, src).trim(),
1232 "static_cast" | "reinterpret_cast" | "dynamic_cast" | "const_cast"
1233 ) {
1234 return None;
1235 }
1236 let args = func.child_by_field_name("arguments")?;
1237 cpp_type_descriptor_name(&args, src)
1238 }
1239 _ => None,
1240 }
1241}
1242
1243fn cpp_type_descriptor_name(node: &Node<'_>, src: &[u8]) -> Option<String> {
1246 let ti = if node.kind() == "type_identifier" {
1247 *node
1248 } else {
1249 cpp_first_descendant_of_kind(node, "type_identifier")?
1250 };
1251 canonical_cpp_base_name(node_text(&ti, src))
1252}
1253
1254fn cpp_first_descendant_of_kind<'a>(node: &Node<'a>, kind: &str) -> Option<Node<'a>> {
1256 let mut stack = vec![*node];
1257 while let Some(n) = stack.pop() {
1258 let mut cursor = n.walk();
1259 for child in n.named_children(&mut cursor) {
1260 if child.kind() == kind {
1261 return Some(child);
1262 }
1263 stack.push(child);
1264 }
1265 }
1266 None
1267}
1268
1269fn cpp_enclosing_fn_span(node: &Node<'_>, file: FileId) -> Option<bonsai_common::Span> {
1272 let mut cur = node.parent();
1273 while let Some(n) = cur {
1274 if n.kind() == "function_definition" {
1275 return Some(span_of(file, &n));
1276 }
1277 cur = n.parent();
1278 }
1279 None
1280}
1281
1282fn canonical_cpp_base_name(raw: &str) -> Option<String> {
1286 let trimmed = raw.trim();
1287 let without_template = trimmed.split('<').next().unwrap_or(trimmed).trim();
1288 let bare = without_template
1289 .rsplit("::")
1290 .next()
1291 .unwrap_or(without_template)
1292 .trim();
1293 if bare.is_empty() {
1294 return None;
1295 }
1296 Some(bare.to_string())
1297}
1298
1299fn parse_imports(tree: &Tree, src: &[u8], file: FileId) -> Vec<ImportSpec> {
1303 let mut imports = c_family_preproc_imports(tree, src, file);
1304 for using_node in collect_kinds(tree, &["using_declaration"]) {
1306 let is_wildcard_namespace = (0..using_node.child_count())
1315 .filter_map(|index| u32::try_from(index).ok())
1316 .any(|index| {
1317 using_node
1318 .child(index)
1319 .is_some_and(|child| child.kind() == "namespace")
1320 });
1321 let mut path_cursor = using_node.walk();
1322 let Some(path_node) = using_node
1323 .named_children(&mut path_cursor)
1324 .find(|child| matches!(child.kind(), "identifier" | "qualified_identifier"))
1325 else {
1326 continue;
1327 };
1328 let mut path_segments = cpp_import_path_segments(path_node, src);
1329 if path_segments.is_empty() {
1330 continue;
1331 }
1332 if is_wildcard_namespace {
1333 imports.push(ImportSpec {
1334 span: span_of(file, &using_node),
1335 module: path_segments.join("::"),
1336 alias: None,
1337 is_wildcard: true,
1338 original_name: None,
1339 scope: ImportScope::Module,
1340 });
1341 } else if let Some(original_name) = path_segments.pop() {
1342 imports.push(ImportSpec {
1343 span: span_of(file, &using_node),
1344 module: path_segments.join("::"),
1345 alias: None,
1346 is_wildcard: false,
1347 original_name: Some(original_name),
1348 scope: ImportScope::Module,
1349 });
1350 }
1351 }
1352 for alias_node in collect_kinds(tree, &["namespace_alias_definition"]) {
1358 let alias_name_node = alias_node.child_by_field_name("name").or_else(|| {
1359 let mut cursor = alias_node.walk();
1360 let mut found = None;
1361 for child in alias_node.named_children(&mut cursor) {
1362 if matches!(child.kind(), "identifier" | "namespace_identifier") {
1363 found = Some(child);
1364 break;
1365 }
1366 }
1367 found
1368 });
1369 let module_name_node = alias_node
1370 .child_by_field_name("aliased")
1371 .or_else(|| alias_node.child_by_field_name("value"))
1372 .or_else(|| {
1373 let alias_name_node = alias_name_node?;
1374 let mut cursor = alias_node.walk();
1375 let target = alias_node.named_children(&mut cursor).find(|child| {
1376 *child != alias_name_node
1377 && matches!(
1378 child.kind(),
1379 "namespace_identifier" | "nested_namespace_specifier"
1380 )
1381 });
1382 target
1383 });
1384 let (Some(alias_name_node), Some(module_name_node)) = (alias_name_node, module_name_node) else {
1385 continue;
1386 };
1387 let alias_name = node_text(&alias_name_node, src).trim().to_string();
1388 let module = node_text(&module_name_node, src).trim().to_string();
1389 if alias_name.is_empty() || module.is_empty() || alias_name == module {
1390 continue;
1391 }
1392 imports.push(ImportSpec {
1393 span: span_of(file, &alias_node),
1394 module,
1395 alias: Some(alias_name),
1396 is_wildcard: false,
1397 original_name: None,
1398 scope: ImportScope::Module,
1399 });
1400 }
1401 imports
1402}
1403
1404fn cpp_import_path_segments(path: Node<'_>, src: &[u8]) -> Vec<String> {
1405 if path.kind() == "qualified_identifier" {
1406 let mut segments = path
1407 .child_by_field_name("scope")
1408 .map(|scope| cpp_import_path_segments(scope, src))
1409 .unwrap_or_default();
1410 if let Some(name) = path.child_by_field_name("name") {
1411 segments.extend(cpp_import_path_segments(name, src));
1412 }
1413 return segments;
1414 }
1415 if path.kind() == "nested_namespace_specifier" {
1416 let mut segments = Vec::new();
1417 let mut cursor = path.walk();
1418 for child in path.named_children(&mut cursor) {
1419 segments.extend(cpp_import_path_segments(child, src));
1420 }
1421 return segments;
1422 }
1423 let segment = node_text(&path, src).trim();
1424 if segment.is_empty() {
1425 Vec::new()
1426 } else {
1427 vec![segment.to_string()]
1428 }
1429}
1430
1431fn fix_cpp_catch_params(events: &mut [bonsai_lang_api::FlowEvent], tree: &Tree, src: &[u8]) {
1438 use bonsai_lang_api::FlowEvent;
1439 for event in events {
1440 match event {
1441 FlowEvent::Try {
1442 span,
1443 body,
1444 catch_events,
1445 finally_events,
1446 catch_param,
1447 ..
1448 } => {
1449 if let Some(node) =
1450 bonsai_lang_api::kit::node_at_span(tree.root_node(), *span, &["try_statement"])
1451 {
1452 if let Some(name) = cpp_catch_param_binding(node, src) {
1453 *catch_param = Some(name);
1454 }
1455 }
1456 fix_cpp_catch_params(body, tree, src);
1457 fix_cpp_catch_params(catch_events, tree, src);
1458 fix_cpp_catch_params(finally_events, tree, src);
1459 }
1460 FlowEvent::Branch {
1461 then_events,
1462 else_events,
1463 ..
1464 } => {
1465 fix_cpp_catch_params(then_events, tree, src);
1466 fix_cpp_catch_params(else_events, tree, src);
1467 }
1468 FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
1469 fix_cpp_catch_params(body, tree, src);
1470 }
1471 _ => {}
1472 }
1473 }
1474}
1475
1476fn cpp_catch_param_binding(try_node: Node<'_>, src: &[u8]) -> Option<String> {
1477 let mut tcur = try_node.walk();
1478 for child in try_node.named_children(&mut tcur) {
1479 if child.kind() != "catch_clause" {
1480 continue;
1481 }
1482 let mut ccur = child.walk();
1484 for sub in child.named_children(&mut ccur) {
1485 let target = if sub.kind() == "parameter_list" {
1486 let mut pcur = sub.walk();
1487 let mut found: Option<Node<'_>> = None;
1488 for c in sub.named_children(&mut pcur) {
1489 if c.kind() == "parameter_declaration" {
1490 found = Some(c);
1491 break;
1492 }
1493 }
1494 found
1495 } else if sub.kind() == "parameter_declaration" {
1496 Some(sub)
1497 } else {
1498 None
1499 };
1500 let Some(pdecl) = target else { continue };
1501 let decl = pdecl.child_by_field_name("declarator");
1506 if let Some(decl) = decl {
1507 if let Some(ident) = first_identifier_descendant_cpp(decl) {
1508 return Some(node_text(&ident, src).trim().to_string());
1509 }
1510 }
1511 let mut pcur = pdecl.walk();
1513 let mut last_ident: Option<Node<'_>> = None;
1514 for n in pdecl.named_children(&mut pcur) {
1515 if let Some(found) = first_identifier_descendant_cpp(n) {
1516 last_ident = Some(found);
1517 }
1518 }
1519 if let Some(n) = last_ident {
1520 return Some(node_text(&n, src).trim().to_string());
1521 }
1522 }
1523 }
1524 None
1525}
1526
1527fn first_identifier_descendant_cpp<'a>(node: Node<'a>) -> Option<Node<'a>> {
1528 if node.kind() == "identifier" || node.kind() == "field_identifier" {
1529 return Some(node);
1530 }
1531 let mut cursor = node.walk();
1532 for child in node.named_children(&mut cursor) {
1533 if let Some(found) = first_identifier_descendant_cpp(child) {
1534 return Some(found);
1535 }
1536 }
1537 None
1538}
1539
1540#[cfg(test)]
1541mod import_tests {
1542 use super::*;
1543
1544 fn parse_import_specs(src: &str) -> Vec<ImportSpec> {
1545 let language = language_from_pack(PACK_NAME).expect("cpp grammar");
1546 let mut parser = tree_sitter::Parser::new();
1547 parser.set_language(&language).expect("set cpp grammar");
1548 let tree = parser.parse(src.as_bytes(), None).expect("parse cpp source");
1549 parse_imports(&tree, src.as_bytes(), FileId::new(0))
1550 }
1551
1552 #[test]
1553 fn using_declarations_are_lowered_from_cst_nodes() {
1554 let imports = parse_import_specs(
1555 "using /* trivia */ namespace alpha::beta;\n\
1556 using alpha::beta::Thing;\n\
1557 namespace short_name = alpha::beta;\n",
1558 );
1559
1560 assert!(imports
1561 .iter()
1562 .any(|spec| spec.module == "alpha::beta" && spec.is_wildcard));
1563 assert!(imports.iter().any(|spec| {
1564 spec.module == "alpha::beta"
1565 && spec.alias.is_none()
1566 && spec.original_name.as_deref() == Some("Thing")
1567 }));
1568 assert!(imports.iter().any(|spec| {
1569 spec.module == "alpha::beta"
1570 && spec.alias.as_deref() == Some("short_name")
1571 && spec.original_name.is_none()
1572 }));
1573 }
1574}