1mod parse_recovery;
3
4use bonsai_common::{FileId, Span};
5use bonsai_lang_api::{
6 decl_index_with_handler,
7 kit::{
8 collect_kinds, language_from_pack, node_text, package_module_segments_with_workspace_prefix,
9 parse_with, span_of,
10 },
11 AdapterContext, AdapterError, AssignValueKind, CallTargetExtraction, CharacterConstraintDomain,
12 CharacterConstraintFact, CharacterConstraintOutput, CharacterSubstitutionDomain,
13 CharacterSubstitutionFact, ConditionEquality, ConditionExpressionFact, ConditionOperandFact, DeclIndex,
14 DeclKind, FileSnapshot, FiniteLiteralSelectionFact, FlowEvent, GrammarHandler, ImportIndex, ImportScope,
15 ImportSpec, LanguageAdapter, LanguageCapabilities, LanguageId, ParseRecoveryEdit, PatternBindingSite,
16 SameOriginPathConstraintFact, StaticScalarValue, StringCompositionFact, StringCompositionPart,
17 SyntaxTree, TypeAliasBinding, Vfs, Visibility, EMPTY_HANDLER,
18};
19use parse_recovery::java_parse_recovery_edits;
20use tree_sitter::{Language, Node, Tree};
21
22fn java_call_target<'tree>(node: Node<'tree>, src: &[u8]) -> Option<CallTargetExtraction<'tree>> {
23 match node.kind() {
24 "method_invocation" => {
25 let member = node.child_by_field_name("name")?;
26 let member_text = node_text(&member, src).trim();
27 if member_text.is_empty() {
28 return None;
29 }
30 let full_text = node.child_by_field_name("object").map_or_else(
31 || member_text.to_string(),
32 |receiver| format!("{}.{}", node_text(&receiver, src).trim(), member_text),
33 );
34 Some(CallTargetExtraction {
35 node: member,
36 full_text,
37 })
38 }
39 "object_creation_expression" => {
40 let target = node.child_by_field_name("type")?;
41 let full_text = node_text(&target, src).trim();
42 (!full_text.is_empty()).then_some(CallTargetExtraction {
43 node: target,
44 full_text: full_text.to_string(),
45 })
46 }
47 "explicit_constructor_invocation" => {
48 let target = node
49 .child_by_field_name("constructor")
50 .or_else(|| node.named_child(0))?;
51 let full_text = node_text(&target, src).trim();
52 (!full_text.is_empty()).then_some(CallTargetExtraction {
53 node: target,
54 full_text: full_text.to_string(),
55 })
56 }
57 _ => None,
58 }
59}
60
61fn java_foreach_binding(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
62 (node.kind() == "enhanced_for_statement")
63 .then(|| {
64 Some((
65 node.child_by_field_name("name")?,
66 node.child_by_field_name("value")?,
67 ))
68 })
69 .flatten()
70}
71
72fn java_pattern_bindings(node: Node<'_>) -> Vec<PatternBindingSite<'_>> {
73 let Some(condition) = node.child_by_field_name("condition") else {
74 return Vec::new();
75 };
76 let mut sites = Vec::new();
77
78 let mut stack = vec![condition];
79 while let Some(current) = stack.pop() {
80 if current.kind() == "instanceof_expression" {
81 if let Some(source) = current.child_by_field_name("left") {
82 if let Some(name) = current.child_by_field_name("name") {
83 sites.push(PatternBindingSite {
84 span_node: current,
85 pattern: name,
86 source,
87 });
88 }
89 if let Some(pattern) = current.child_by_field_name("pattern") {
90 java_pattern_binding_identifiers(pattern, &mut sites, current, source);
91 }
92 }
93 continue;
94 }
95 let mut cursor = current.walk();
96 stack.extend(current.named_children(&mut cursor));
97 }
98
99 if let Some(body) = node.child_by_field_name("body") {
100 let mut stack = vec![body];
101 while let Some(current) = stack.pop() {
102 if current.kind() == "switch_label" {
103 let mut cursor = current.walk();
104 for pattern in current.named_children(&mut cursor) {
105 java_pattern_binding_identifiers(pattern, &mut sites, current, condition);
106 }
107 continue;
108 }
109 let mut cursor = current.walk();
110 stack.extend(current.named_children(&mut cursor));
111 }
112 }
113 sites
114}
115
116fn java_pattern_binding_identifiers<'tree>(
117 pattern: Node<'tree>,
118 out: &mut Vec<PatternBindingSite<'tree>>,
119 span_node: Node<'tree>,
120 source: Node<'tree>,
121) {
122 if matches!(pattern.kind(), "type_pattern" | "record_pattern_component") {
123 let mut cursor = pattern.walk();
124 if let Some(name) = pattern
125 .named_children(&mut cursor)
126 .filter(|child| child.kind() == "identifier")
127 .last()
128 {
129 out.push(PatternBindingSite {
130 span_node,
131 pattern: name,
132 source,
133 });
134 }
135 return;
136 }
137 if !matches!(pattern.kind(), "record_pattern" | "record_pattern_body") {
138 return;
139 }
140 let mut cursor = pattern.walk();
141 for child in pattern.named_children(&mut cursor) {
142 java_pattern_binding_identifiers(child, out, span_node, source);
143 }
144}
145
146pub const LANG_ID: LanguageId = LanguageId::new("java");
147const PACK_NAME: &str = "java";
148const MODULE_SOURCE_ROOTS: &[&[&str]] = &[
149 &["src", "main", "java"],
150 &["src", "test", "java"],
151 &["src", "java"],
152];
153
154const HANDLER: GrammarHandler = GrammarHandler {
155 expression_value_kind_extractor: None,
156 literal_value_kinds: &[
157 "null_literal",
158 "boolean_literal",
159 "decimal_integer_literal",
160 "hex_integer_literal",
161 "octal_integer_literal",
162 "binary_integer_literal",
163 "decimal_floating_point_literal",
164 "hex_floating_point_literal",
165 "true",
166 "false",
167 ],
168 string_literal_kinds: &["string_literal", "character_literal", "template_expression"],
169 comment_kinds: &["line_comment", "block_comment"],
170 doc_comment_prefixes: &["/**"],
171 decorator_kinds: &[
172 "annotation",
173 "marker_annotation",
174 "normal_annotation",
175 "single_element_annotation",
176 ],
177 parameter_container_kinds: &["formal_parameters"],
178 parameter_kinds: &["formal_parameter", "spread_parameter", "receiver_parameter"],
179 parameter_modifier_kinds: &["modifiers"],
180 parameter_annotation_kinds: &[
181 "annotation",
182 "marker_annotation",
183 "normal_annotation",
184 "single_element_annotation",
185 ],
186 variadic_parameter_kinds: &["spread_parameter"],
187 binding_identifier_kinds: &["identifier"],
188 pattern_binding_extractor: Some(java_pattern_bindings),
189 identifier_kinds: &["identifier"],
190 positional_aggregate_kinds: &["array_initializer", "array_creation_expression"],
191 aggregate_value_field_names: &["value"],
192 aggregate_syntax_only_kinds: &["type_identifier"],
193 transparent_call_wrapper_kinds: &["field_access", "parenthesized_expression"],
194 assignment_target_wrapper_kinds: &["variable_declarator"],
195 binding_declaration_keyword_spellings: &["final"],
196 fn_kinds: &["method_declaration", "constructor_declaration"],
197 call_kinds: &[
198 "method_invocation",
199 "object_creation_expression",
200 "explicit_constructor_invocation",
201 ],
202 constructor_call_kinds: &["object_creation_expression", "explicit_constructor_invocation"],
203 call_callee_field_names: &["name", "constructor"],
204 call_receiver_field_names: &["object"],
205 call_member_field_names: &["name", "constructor"],
206 constructor_type_field_names: &["type"],
207 call_target_extractor: Some(java_call_target),
208 call_argument_field_names: &["arguments"],
209 call_argument_container_kinds: &["argument_list"],
210 lambda_body_field_names: &["body"],
211 argument_passing_mode_extractor: None,
212 constructor_names: bonsai_lang_api::NO_CONSTRUCTOR_METHOD_NAMES,
213 runtime_type_guard_operators: &["instanceof"],
214 runtime_type_wrapper_kinds: &["parenthesized_expression"],
215 call_ref_kinds: &[
216 "method_invocation",
217 "object_creation_expression",
218 "explicit_constructor_invocation",
219 "method_reference",
220 ],
221 callable_reference_kinds: &["method_reference"],
222 member_expression_kinds: &["field_access"],
223 subscript_expression_kinds: &["array_access"],
224 member_base_field_names: &["object"],
225 member_name_field_names: &["field"],
226 subscript_base_field_names: &["array"],
227 subscript_index_field_names: &["index"],
228 class_kinds: &[
229 "class_declaration",
230 "interface_declaration",
231 "enum_declaration",
232 "annotation_type_declaration",
233 "record_declaration",
234 ],
235 class_decl_kinds: &[
236 ("interface_declaration", DeclKind::Interface),
237 ("annotation_type_declaration", DeclKind::Interface),
238 ("enum_declaration", DeclKind::Enum),
239 ("record_declaration", DeclKind::Class),
240 ("class_declaration", DeclKind::Class),
241 ],
242 method_owner_barrier_kinds: &["object_creation_expression"],
243 method_kinds: &["method_declaration"],
244 method_context_kinds: &[
245 "class_declaration",
246 "interface_declaration",
247 "enum_declaration",
248 "annotation_type_declaration",
249 "record_declaration",
250 ],
251 constructor_method_kinds: &["constructor_declaration"],
252 if_kinds: &["if_statement", "switch_expression"],
253 branch_then_field_names: &["consequence", "body"],
254 branch_else_field_names: &["alternative"],
255 branch_condition_field_names: &["condition", "value"],
256 loop_body_field_names: &["body"],
257 loop_body_kinds: &["block", "expression_statement"],
258 branch_arm_kinds: &["block", "expression_statement", "switch_block_statement_group"],
259 for_kinds: &["for_statement"],
260 foreach_kinds: &["enhanced_for_statement"],
261 foreach_binding_extractor: Some(java_foreach_binding),
262 while_kinds: &["while_statement"],
263 do_kinds: &["do_statement"],
264 assignment_kinds: &["assignment_expression", "variable_declarator", "resource"],
271 compound_assignment_operators: &[
272 "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|=",
273 ],
274 return_kinds: &["return_statement"],
275 throw_kinds: &["throw_statement"],
276 lambda_kinds: &["lambda_expression"],
277 try_kinds: &["try_statement", "try_with_resources_statement"],
278 catch_kinds: &["catch_clause"],
279 finally_kinds: &["finally_clause"],
280 break_kinds: &["break_statement"],
281 continue_kinds: &["continue_statement"],
282 control_label_field_names: &["label"],
283 yield_kinds: &["yield_statement"],
284 yield_value_field_names: &["value"],
285 try_body_field_names: &["body"],
286 implicit_receiver_names: &["this", "super"],
287 ..EMPTY_HANDLER
288};
289
290#[derive(Debug, Default, Copy, Clone)]
291pub struct JavaAdapter;
292
293impl JavaAdapter {
294 #[must_use]
295 pub fn new() -> Self {
296 Self
297 }
298}
299
300impl LanguageAdapter for JavaAdapter {
301 fn language_id(&self) -> LanguageId {
302 LANG_ID
303 }
304 fn display_name(&self) -> &'static str {
305 "Java"
306 }
307 fn file_extensions(&self) -> &'static [&'static str] {
308 &["java"]
309 }
310 fn tree_sitter_language(&self) -> Result<Language, AdapterError> {
311 language_from_pack(PACK_NAME)
312 }
313 fn parse_recovery_edits(
314 &self,
315 snapshot: &FileSnapshot,
316 _vfs: &Vfs,
317 tree: &SyntaxTree,
318 ) -> Vec<ParseRecoveryEdit> {
319 java_parse_recovery_edits(snapshot, tree)
320 }
321 fn capabilities(&self) -> LanguageCapabilities {
322 LanguageCapabilities {
335 module_default_export_names: &[],
336 universal_type_names: &["Object"],
337 module_path_syntax: bonsai_lang_api::ModulePathSyntax::none(),
338 exceptions: bonsai_lang_api::CapabilityLevel::Exact,
339 reflection: bonsai_lang_api::CapabilityLevel::Partial,
340 receiver_types: bonsai_lang_api::CapabilityLevel::Partial,
341 field_places_complete: true,
342 constructor_method_names: bonsai_lang_api::NO_CONSTRUCTOR_METHOD_NAMES,
346 super_receiver_tokens: &["super"],
347 implicit_receiver_tokens: &["this"],
348 receiver_type_syntax: bonsai_lang_api::ReceiverTypeSyntax {
349 wrapper_calls: &[],
350 class_object_suffixes: &[".class"],
351 },
352 call_text_prefilter: bonsai_lang_api::CallTextPrefilter::Parenthesized,
353 ..LanguageCapabilities::partial_baseline()
354 }
355 }
356 fn extract_declarations(&self, file: FileId, ctx: &AdapterContext<'_>) -> DeclIndex {
357 let mut index = decl_index_with_handler(PACK_NAME, file, ctx, &HANDLER);
358 let Some((snapshot, tree)) = parse_with(PACK_NAME, file, ctx) else {
359 return index;
360 };
361 let src = snapshot.text.as_bytes();
362 populate_java_condition_expressions(&mut index.branch_conditions, &tree, file, src);
363 populate_java_static_scalar_facts(&mut index, &tree, file, src);
364 populate_java_immutable_assignment_facts(&mut index, &tree, file, src);
365 index.string_compositions = java_string_compositions(&tree, file, src);
366 index.finite_literal_selections = java_finite_literal_selections(&index, &tree, file, src);
367 index.character_substitutions = java_character_substitutions(&index.defs, &tree, file, src);
368 index.character_constraints = bonsai_lang_api::character_constraints_from_substitutions(
369 &index.defs,
370 &index.character_substitutions,
371 );
372 index.same_origin_path_constraints = java_same_origin_path_constraints(&index, &tree, file, src);
373 index
374 .character_constraints
375 .extend(java_compiled_pattern_constraints(&index, &tree, file, src));
376 index
377 .character_constraints
378 .sort_by_key(|fact| (fact.transform_span.start, fact.transform_span.end));
379 index
380 .character_constraints
381 .dedup_by_key(|fact| fact.transform_span);
382 bonsai_lang_api::populate_decl_return_types(&mut index, &tree, src, &HANDLER);
385 for decl in &mut index.defs {
389 populate_java_exception_types(&mut decl.flow_events, &tree, src);
390 rewrite_java_reflection_chain(&mut decl.flow_events);
391 }
392 let field_aliases = collect_java_type_aliases(tree.root_node(), src, &["field_declaration"]);
393 let method_aliases = collect_java_method_type_aliases(&tree, file, src, &field_aliases);
394 for decl in &mut index.defs {
395 if let Some(aliases) = method_aliases
396 .iter()
397 .find_map(|(span, aliases)| (*span == decl.span).then_some(aliases))
398 {
399 decl.type_aliases = aliases.clone();
400 }
401 }
402 attach_java_nested_callable_type_aliases(&mut index, &tree, file, src);
403 let class_type_bounds = collect_java_class_type_parameter_bounds(&tree, file, src);
409 let bounds_by_parent: std::collections::HashMap<_, _> = index
410 .defs
411 .iter()
412 .filter(|decl| is_class_like(decl.kind))
413 .filter_map(|decl| {
414 class_type_bounds
415 .iter()
416 .find_map(|(span, bounds)| (*span == decl.span).then(|| (decl.symbol, bounds.clone())))
417 })
418 .collect();
419 for decl in &mut index.defs {
420 if let Some(bounds) = decl.parent.and_then(|parent| bounds_by_parent.get(&parent)) {
421 expand_java_type_parameter_aliases(&mut decl.type_aliases, bounds);
422 }
423 }
424 let bases_by_span = collect_java_class_bases(&tree, file, src);
429 for decl in &mut index.defs {
430 if !is_class_like(decl.kind) {
431 continue;
432 }
433 if let Some(bases) = bases_by_span
434 .iter()
435 .find_map(|(span, bases)| (*span == decl.span).then_some(bases))
436 {
437 decl.bases = bases.clone();
438 }
439 }
440 qualify_java_instance_field_receivers(&mut index, &tree, src);
441 rewrite_java_explicit_constructor_invocations(&mut index);
442 let constants_by_class = collect_java_class_string_constants(&tree, file, src);
443 attach_java_class_string_constants(&mut index, &constants_by_class);
444 let visibility_by_span = collect_java_visibility(tree.root_node(), file, src);
447 for decl in &mut index.defs {
448 if let Some(vis) = visibility_by_span.get(&decl.span).copied() {
449 decl.visibility = vis;
450 }
451 }
452 if let Some(segments) = extract_java_package(tree.root_node(), src) {
459 let segments =
460 package_module_segments_with_workspace_prefix(file, ctx, segments, MODULE_SOURCE_ROOTS);
461 bonsai_lang_api::apply_module_path_semantic_identity(&mut index, segments);
462 } else {
463 bonsai_lang_api::apply_module_path_semantic_identity(&mut index, Vec::new());
464 }
465 for decl in &mut index.defs {
466 bonsai_lang_api::normalize_call_result_assignment_sources(&mut decl.flow_events);
467 }
468 bonsai_lang_api::kit::synthesize_record_members(&mut index, &tree, src, file);
475 bonsai_lang_api::kit::apply_lexical_member_qualified_names(&mut index, ".");
476 bonsai_lang_api::kit::qualify_bare_hierarchy_member_calls(&mut index);
477 bonsai_lang_api::apply_constructor_result_type_aliases(&mut index);
487 bonsai_lang_api::apply_class_field_type_aliases(&mut index);
488 index
489 }
490 fn extract_imports(&self, file: FileId, ctx: &AdapterContext<'_>) -> ImportIndex {
491 let Some((snapshot, tree)) = parse_with(PACK_NAME, file, ctx) else {
492 return ImportIndex {
493 file,
494 ..Default::default()
495 };
496 };
497 ImportIndex {
498 file,
499 imports: collect_java_imports(&tree, file, snapshot.text.as_bytes()),
500 }
501 }
502}
503
504fn populate_java_immutable_assignment_facts(index: &mut DeclIndex, tree: &Tree, file: FileId, src: &[u8]) {
505 for field in collect_kinds(tree, &["field_declaration"]) {
506 if !java_declaration_has_final_modifier(field) {
507 continue;
508 }
509 let field_span = span_of(file, &field);
510 let owner = index
511 .defs
512 .iter()
513 .filter(|decl| {
514 is_class_like(decl.kind)
515 && decl.span.start <= field_span.start
516 && field_span.end <= decl.span.end
517 })
518 .min_by_key(|decl| decl.span.len())
519 .map(|decl| decl.symbol);
520 for declarator in java_collect_kinds_below(field, &["variable_declarator"]) {
521 let (Some(name), Some(value)) = (
522 declarator.child_by_field_name("name"),
523 declarator.child_by_field_name("value"),
524 ) else {
525 continue;
526 };
527 let name_span = span_of(file, &name);
528 let name_text = node_text(&name, src);
529 let value_span = span_of(file, &value);
530 if let Some(fact) = index.assignment_values.iter_mut().find(|fact| {
531 fact.target_span == Some(name_span)
532 || (fact.value_span == value_span && fact.target.as_deref() == Some(name_text.trim()))
533 }) {
534 fact.target_is_immutable = true;
535 fact.target_owner = owner;
536 }
537 }
538 }
539}
540
541fn java_same_origin_path_constraints(
542 index: &DeclIndex,
543 tree: &Tree,
544 file: FileId,
545 src: &[u8],
546) -> Vec<SameOriginPathConstraintFact> {
547 let mut facts = Vec::new();
548 for method in collect_kinds(tree, &["method_declaration"]) {
549 let method_span = span_of(file, &method);
550 let Some(decl) = index.defs.iter().find(|decl| decl.span == method_span) else {
551 continue;
552 };
553 let Some(body) = method.child_by_field_name("body") else {
554 continue;
555 };
556 let mut cursor = body.walk();
557 let statements = body.named_children(&mut cursor).collect::<Vec<_>>();
558 let [guard, final_return] = statements.as_slice() else {
559 continue;
560 };
561 if guard.kind() != "if_statement"
562 || guard.child_by_field_name("alternative").is_some()
563 || final_return.kind() != "return_statement"
564 {
565 continue;
566 }
567 let (Some(condition), Some(consequence), Some(return_value)) = (
568 guard.child_by_field_name("condition"),
569 guard.child_by_field_name("consequence"),
570 final_return.named_child(0),
571 ) else {
572 continue;
573 };
574 let fallback_returns = java_collect_kinds_below(consequence, &["return_statement"]);
575 let [fallback_return] = fallback_returns.as_slice() else {
576 continue;
577 };
578 if fallback_return
579 .named_child(0)
580 .and_then(|value| java_static_string_literal(value, src))
581 .as_deref()
582 != Some("/")
583 {
584 continue;
585 }
586 for (input_param_index, parameter) in decl.params.iter().enumerate() {
587 if return_value.kind() != "identifier" || node_text(&return_value, src).trim() != parameter {
588 continue;
589 }
590 let mut terms = Vec::new();
591 java_collect_logical_terms(condition, "||", src, &mut terms);
592 let requires_absolute_path = terms
593 .iter()
594 .any(|term| java_starts_with_literal(*term, parameter, "/", true, src));
595 let rejects_scheme_relative_path = terms
596 .iter()
597 .any(|term| java_starts_with_literal(*term, parameter, "//", false, src));
598 if requires_absolute_path && rejects_scheme_relative_path {
599 facts.push(SameOriginPathConstraintFact {
600 function_span: decl.span,
601 guard_span: span_of(file, guard),
602 input_place: parameter.clone(),
603 input_param_index: Some(input_param_index),
604 provider_call: None,
605 rejects_scheme: true,
606 rejects_authority: true,
607 requires_absolute_path,
608 rejects_scheme_relative_path,
609 });
610 }
611 }
612 }
613 facts.sort_by_key(|fact| (fact.function_span.start, fact.guard_span.start));
614 facts.dedup();
615 facts
616}
617
618fn java_collect_kinds_below<'tree>(root: Node<'tree>, kinds: &[&str]) -> Vec<Node<'tree>> {
619 let mut out = Vec::new();
620 let mut stack = vec![root];
621 while let Some(node) = stack.pop() {
622 if kinds.contains(&node.kind()) {
623 out.push(node);
624 continue;
625 }
626 let mut cursor = node.walk();
627 stack.extend(node.named_children(&mut cursor));
628 }
629 out
630}
631
632fn java_collect_logical_terms<'tree>(
633 expression: Node<'tree>,
634 operator: &str,
635 src: &[u8],
636 out: &mut Vec<Node<'tree>>,
637) {
638 let expression = java_unwrap_parenthesized(expression);
639 let operands = (
640 expression.child_by_field_name("left"),
641 expression.child_by_field_name("right"),
642 );
643 if expression.kind() == "binary_expression"
644 && operands.0.zip(operands.1).is_some_and(|(left, right)| {
645 src.get(left.end_byte()..right.start_byte())
646 .and_then(|bytes| std::str::from_utf8(bytes).ok())
647 .is_some_and(|value| value.trim() == operator)
648 })
649 {
650 let (Some(left), Some(right)) = operands else {
651 return;
652 };
653 java_collect_logical_terms(left, operator, src, out);
654 java_collect_logical_terms(right, operator, src, out);
655 } else {
656 out.push(expression);
657 }
658}
659
660fn java_unwrap_parenthesized(mut expression: Node<'_>) -> Node<'_> {
661 while expression.kind() == "parenthesized_expression" && expression.named_child_count() == 1 {
662 let Some(inner) = expression.named_child(0) else {
663 break;
664 };
665 expression = inner;
666 }
667 expression
668}
669
670fn java_starts_with_literal(
671 expression: Node<'_>,
672 receiver: &str,
673 literal: &str,
674 negated: bool,
675 src: &[u8],
676) -> bool {
677 let expression = java_unwrap_parenthesized(expression);
678 let call = if negated {
679 if expression.kind() != "unary_expression" {
680 return false;
681 }
682 let Some(operand) = expression
683 .child_by_field_name("operand")
684 .or_else(|| expression.named_child(0))
685 else {
686 return false;
687 };
688 if src
689 .get(expression.start_byte()..operand.start_byte())
690 .and_then(|bytes| std::str::from_utf8(bytes).ok())
691 .is_none_or(|prefix| prefix.trim() != "!")
692 {
693 return false;
694 }
695 java_unwrap_parenthesized(operand)
696 } else {
697 expression
698 };
699 if call.kind() != "method_invocation"
700 || call
701 .child_by_field_name("object")
702 .is_none_or(|object| object.kind() != "identifier" || node_text(&object, src).trim() != receiver)
703 || call
704 .child_by_field_name("name")
705 .is_none_or(|name| node_text(&name, src).trim() != "startsWith")
706 {
707 return false;
708 }
709 let Some(arguments) = call.child_by_field_name("arguments") else {
710 return false;
711 };
712 let mut cursor = arguments.walk();
713 let values = arguments.named_children(&mut cursor).collect::<Vec<_>>();
714 let [value] = values.as_slice() else {
715 return false;
716 };
717 java_static_string_literal(*value, src).as_deref() == Some(literal)
718}
719
720fn java_character_substitutions(
721 defs: &[bonsai_lang_api::Decl],
722 tree: &Tree,
723 file: FileId,
724 src: &[u8],
725) -> Vec<CharacterSubstitutionFact> {
726 let mut facts = Vec::new();
727 for return_node in collect_kinds(tree, &["return_statement"]) {
728 let return_span = span_of(file, &return_node);
729 let Some(decl) = defs
730 .iter()
731 .filter(|decl| {
732 matches!(decl.kind, DeclKind::Method | DeclKind::Constructor)
733 && decl.span.start <= return_span.start
734 && return_span.end <= decl.span.end
735 })
736 .min_by_key(|decl| decl.span.len())
737 else {
738 continue;
739 };
740 let Some(expression) = return_node.named_child(0) else {
741 continue;
742 };
743 let Some((input_param_index, exact_mappings, characters)) =
744 java_inline_replace_chain(expression, &decl.params, src)
745 else {
746 continue;
747 };
748 facts.push(CharacterSubstitutionFact {
749 function_span: decl.span,
750 transform_span: return_span,
751 input_param_index,
752 exact_mappings,
753 table: String::new(),
754 domain: CharacterSubstitutionDomain::ExactCharacters { characters },
755 });
756 }
757 for method in collect_kinds(tree, &["method_declaration"]) {
758 let method_span = span_of(file, &method);
759 let Some(decl) = defs.iter().find(|decl| decl.span == method_span) else {
760 continue;
761 };
762 let Some((input_param_index, transform_span, exact_mappings)) =
763 java_switch_character_substitution(method, decl, file, src)
764 else {
765 continue;
766 };
767 facts.push(CharacterSubstitutionFact {
768 function_span: decl.span,
769 transform_span,
770 input_param_index,
771 exact_mappings,
772 table: String::new(),
773 domain: CharacterSubstitutionDomain::TableKeysWithIdentityFallback,
774 });
775 }
776 facts.sort_by_key(|fact| (fact.function_span.start, fact.transform_span.start));
777 facts.dedup();
778 facts
779}
780
781fn java_switch_character_substitution(
787 method: Node<'_>,
788 decl: &bonsai_lang_api::Decl,
789 file: FileId,
790 src: &[u8],
791) -> Option<(
792 usize,
793 bonsai_common::Span,
794 Vec<bonsai_lang_api::StaticStringMapEntry>,
795)> {
796 let body = method.child_by_field_name("body")?;
797 let statements = body.named_children(&mut body.walk()).collect::<Vec<_>>();
798 let [builder_decl, loop_node, return_node] = statements.as_slice() else {
799 return None;
800 };
801 if builder_decl.kind() != "local_variable_declaration"
802 || loop_node.kind() != "enhanced_for_statement"
803 || return_node.kind() != "return_statement"
804 {
805 return None;
806 }
807 let declarators = builder_decl
808 .named_children(&mut builder_decl.walk())
809 .filter(|node| node.kind() == "variable_declarator")
810 .collect::<Vec<_>>();
811 let [declarator] = declarators.as_slice() else {
812 return None;
813 };
814 let builder = declarator.child_by_field_name("name")?;
815 let initializer = declarator.child_by_field_name("value")?;
816 if builder.kind() != "identifier" || initializer.kind() != "object_creation_expression" {
817 return None;
818 }
819 let builder = node_text(&builder, src).trim();
820 let builder_type = initializer.child_by_field_name("type")?;
821 if node_text(&builder_type, src).trim() != "StringBuilder" {
822 return None;
823 }
824
825 let loop_variable = loop_node.child_by_field_name("name")?;
826 let iterated = loop_node.child_by_field_name("value")?;
827 let loop_body = loop_node.child_by_field_name("body")?;
828 if loop_variable.kind() != "identifier" || iterated.kind() != "method_invocation" {
829 return None;
830 }
831 let loop_variable = node_text(&loop_variable, src).trim();
832 let iterated_object = iterated.child_by_field_name("object")?;
833 let iterated_method = iterated.child_by_field_name("name")?;
834 let input = node_text(&iterated_object, src).trim();
835 if iterated_object.kind() != "identifier"
836 || node_text(&iterated_method, src).trim() != "toCharArray"
837 || iterated
838 .child_by_field_name("arguments")
839 .is_none_or(|args| args.named_child_count() != 0)
840 {
841 return None;
842 }
843 let input_param_index = decl.params.iter().position(|parameter| parameter == input)?;
844 let switches = java_collect_kinds_below(loop_body, &["switch_expression"]);
845 let [switch_node] = switches.as_slice() else {
846 return None;
847 };
848 let condition = switch_node.child_by_field_name("condition")?;
849 if node_text(&condition, src)
850 .trim()
851 .trim_start_matches('(')
852 .trim_end_matches(')')
853 .trim()
854 != loop_variable
855 {
856 return None;
857 }
858 let switch_body = switch_node.child_by_field_name("body")?;
859 let groups = switch_body
860 .named_children(&mut switch_body.walk())
861 .collect::<Vec<_>>();
862 if groups.is_empty() {
863 return None;
864 }
865 let mut mappings = Vec::new();
866 let mut saw_identity_default = false;
867 for group in groups {
868 if group.kind() != "switch_block_statement_group" {
869 return None;
870 }
871 let children = group.named_children(&mut group.walk()).collect::<Vec<_>>();
872 let label = children
873 .first()
874 .copied()
875 .filter(|node| node.kind() == "switch_label")?;
876 let invocations = java_collect_kinds_below(group, &["method_invocation"]);
877 let [append] = invocations.as_slice() else {
878 return None;
879 };
880 let append_object = append.child_by_field_name("object")?;
881 let append_name = append.child_by_field_name("name")?;
882 let append_args = append.child_by_field_name("arguments")?;
883 let values = append_args
884 .named_children(&mut append_args.walk())
885 .collect::<Vec<_>>();
886 let [value] = values.as_slice() else {
887 return None;
888 };
889 if node_text(&append_object, src).trim() != builder || node_text(&append_name, src).trim() != "append"
890 {
891 return None;
892 }
893 let label_values = label.named_children(&mut label.walk()).collect::<Vec<_>>();
894 if label_values.is_empty() {
895 if saw_identity_default
896 || value.kind() != "identifier"
897 || node_text(value, src).trim() != loop_variable
898 || children.len() != 2
899 {
900 return None;
901 }
902 saw_identity_default = true;
903 continue;
904 }
905 let [label_value] = label_values.as_slice() else {
906 return None;
907 };
908 let input = java_static_string_or_character(*label_value, src)?;
909 let output = java_static_string_literal(*value, src)?;
910 if input.chars().count() != 1
911 || children.len() != 3
912 || children
913 .last()
914 .is_none_or(|node| node.kind() != "break_statement")
915 || mappings
916 .iter()
917 .any(|entry: &bonsai_lang_api::StaticStringMapEntry| entry.key == input)
918 {
919 return None;
920 }
921 mappings.push(bonsai_lang_api::StaticStringMapEntry {
922 key: input,
923 value: output,
924 });
925 }
926 if !saw_identity_default || mappings.is_empty() {
927 return None;
928 }
929 let returned = return_node.named_child(0)?;
930 if returned.kind() != "method_invocation"
931 || returned
932 .child_by_field_name("object")
933 .is_none_or(|object| node_text(&object, src).trim() != builder)
934 || returned
935 .child_by_field_name("name")
936 .is_none_or(|name| node_text(&name, src).trim() != "toString")
937 || returned
938 .child_by_field_name("arguments")
939 .is_none_or(|args| args.named_child_count() != 0)
940 {
941 return None;
942 }
943 mappings.sort_by(|left, right| left.key.cmp(&right.key));
944 Some((input_param_index, span_of(file, switch_node), mappings))
945}
946
947fn java_inline_replace_chain(
948 expression: Node<'_>,
949 params: &[String],
950 src: &[u8],
951) -> Option<(usize, Vec<bonsai_lang_api::StaticStringMapEntry>, Vec<String>)> {
952 let mut current = expression;
953 let mut mappings = Vec::new();
954 let mut characters = Vec::new();
955 while current.kind() == "method_invocation" {
956 let method = current.child_by_field_name("name")?;
957 let method = node_text(&method, src).trim();
958 if !matches!(method, "replace" | "replaceAll") {
959 break;
960 }
961 let arguments = current.child_by_field_name("arguments")?;
962 let args = arguments
963 .named_children(&mut arguments.walk())
964 .collect::<Vec<_>>();
965 let [pattern, replacement] = args.as_slice() else {
966 return None;
967 };
968 let output = java_static_string_or_character(*replacement, src)?;
969 let replaced = if method == "replace" {
970 let value = java_static_string_or_character(*pattern, src)?;
971 (value.chars().count() == 1).then(|| vec![value])?
972 } else {
973 java_exact_regex_character_class(&java_static_string_literal(*pattern, src)?)?
974 };
975 for input in replaced {
976 if mappings
977 .iter()
978 .any(|entry: &bonsai_lang_api::StaticStringMapEntry| {
979 entry.key == input && entry.value != output
980 })
981 {
982 return None;
983 }
984 if !mappings.iter().any(|entry| entry.key == input) {
985 characters.push(input.clone());
986 mappings.push(bonsai_lang_api::StaticStringMapEntry {
987 key: input,
988 value: output.clone(),
989 });
990 }
991 }
992 current = current.child_by_field_name("object")?;
993 }
994 if mappings.is_empty() || current.kind() != "identifier" {
995 return None;
996 }
997 let input = node_text(¤t, src).trim();
998 let input_param_index = params.iter().position(|param| param == input)?;
999 characters.sort();
1000 characters.dedup();
1001 mappings.sort_by(|left, right| left.key.cmp(&right.key));
1002 Some((input_param_index, mappings, characters))
1003}
1004
1005fn java_static_string_or_character(node: Node<'_>, src: &[u8]) -> Option<String> {
1006 java_static_string_literal(node, src).or_else(|| {
1007 if node.kind() != "character_literal" {
1008 return None;
1009 }
1010 let raw = node_text(&node, src);
1011 let inner = raw.strip_prefix('\'')?.strip_suffix('\'')?;
1012 match inner {
1013 "\\r" => Some("\r".to_string()),
1014 "\\n" => Some("\n".to_string()),
1015 "\\t" => Some("\t".to_string()),
1016 "\\0" => Some("\0".to_string()),
1017 "\\\\" => Some("\\".to_string()),
1018 "\\\"" => Some("\"".to_string()),
1019 _ if inner.chars().count() == 1 => Some(inner.to_string()),
1020 _ => None,
1021 }
1022 })
1023}
1024
1025fn java_exact_regex_character_class(pattern: &str) -> Option<Vec<String>> {
1026 let inner = pattern.strip_prefix('[')?.strip_suffix(']')?;
1027 if inner.starts_with('^') || inner.is_empty() {
1028 return None;
1029 }
1030 let mut characters = Vec::new();
1031 let mut chars = inner.chars();
1032 while let Some(character) = chars.next() {
1033 if character == '-' {
1034 return None;
1035 }
1036 let decoded = if character == '\\' {
1037 match chars.next()? {
1038 'r' => '\r',
1039 'n' => '\n',
1040 't' => '\t',
1041 '\\' => '\\',
1042 '"' => '"',
1043 '\'' => '\'',
1044 _ => return None,
1045 }
1046 } else {
1047 character
1048 };
1049 characters.push(decoded.to_string());
1050 }
1051 characters.sort();
1052 characters.dedup();
1053 Some(characters)
1054}
1055
1056fn java_compiled_pattern_constraints(
1057 index: &DeclIndex,
1058 tree: &Tree,
1059 file: FileId,
1060 src: &[u8],
1061) -> Vec<CharacterConstraintFact> {
1062 let bindings = java_bindings(tree, src);
1063 let mut patterns = Vec::new();
1064 for declarator in collect_kinds(tree, &["variable_declarator"]) {
1065 let (Some(name), Some(value)) = (
1066 declarator.child_by_field_name("name"),
1067 declarator.child_by_field_name("value"),
1068 ) else {
1069 continue;
1070 };
1071 if name.kind() != "identifier" || value.kind() != "method_invocation" {
1072 continue;
1073 }
1074 let (Some(object), Some(method), Some(arguments)) = (
1075 value.child_by_field_name("object"),
1076 value.child_by_field_name("name"),
1077 value.child_by_field_name("arguments"),
1078 ) else {
1079 continue;
1080 };
1081 if node_text(&object, src).trim() != "Pattern" || node_text(&method, src).trim() != "compile" {
1082 continue;
1083 }
1084 let args = arguments
1085 .named_children(&mut arguments.walk())
1086 .collect::<Vec<_>>();
1087 let [pattern] = args.as_slice() else {
1088 continue;
1089 };
1090 let Some(pattern) = java_static_string_literal(*pattern, src) else {
1091 continue;
1092 };
1093 let Some((_, declaration, _)) = java_binding_scope_and_declaration(declarator) else {
1094 continue;
1095 };
1096 if !java_declaration_has_final_modifier(declaration) {
1097 continue;
1098 }
1099 let characters = if pattern == "\\p{Cntrl}" {
1100 vec!["\r".to_string(), "\n".to_string()]
1101 } else if let Some(characters) = java_exact_regex_character_class(&pattern) {
1102 characters
1103 } else {
1104 continue;
1105 };
1106 let name = node_text(&name, src).trim().to_string();
1107 patterns.push((name, span_of(file, &value), characters));
1108 }
1109
1110 let mut facts = Vec::new();
1111 for call in collect_kinds(tree, &["method_invocation"]) {
1112 let (Some(method), Some(receiver), Some(arguments)) = (
1113 call.child_by_field_name("name"),
1114 call.child_by_field_name("object"),
1115 call.child_by_field_name("arguments"),
1116 ) else {
1117 continue;
1118 };
1119 if node_text(&method, src).trim() != "replaceAll" || receiver.kind() != "method_invocation" {
1120 continue;
1121 }
1122 let (Some(matcher_name), Some(pattern_receiver), Some(matcher_args)) = (
1123 receiver.child_by_field_name("name"),
1124 receiver.child_by_field_name("object"),
1125 receiver.child_by_field_name("arguments"),
1126 ) else {
1127 continue;
1128 };
1129 if node_text(&matcher_name, src).trim() != "matcher" || pattern_receiver.kind() != "identifier" {
1130 continue;
1131 }
1132 let pattern_name = node_text(&pattern_receiver, src).trim();
1133 let Some(binding) = bindings.resolve(
1134 pattern_name,
1135 pattern_receiver.start_byte(),
1136 pattern_receiver.end_byte(),
1137 ) else {
1138 continue;
1139 };
1140 let binding_value_span = span_of(file, &binding.initializer);
1141 let Some((_, _, mut characters)) = patterns
1142 .iter()
1143 .find(|(name, value_span, _)| name == pattern_name && *value_span == binding_value_span)
1144 .cloned()
1145 else {
1146 continue;
1147 };
1148 let matcher_args = matcher_args
1149 .named_children(&mut matcher_args.walk())
1150 .collect::<Vec<_>>();
1151 let replace_args = arguments
1152 .named_children(&mut arguments.walk())
1153 .collect::<Vec<_>>();
1154 let ([input], [replacement]) = (matcher_args.as_slice(), replace_args.as_slice()) else {
1155 continue;
1156 };
1157 if input.kind() != "identifier" {
1158 continue;
1159 }
1160 let Some(replacement) = java_static_string_literal(*replacement, src) else {
1161 continue;
1162 };
1163 characters.retain(|character| !replacement.contains(character));
1164 if characters.is_empty() {
1165 continue;
1166 }
1167 let transform_span = span_of(file, &call);
1168 let Some(decl) = index
1169 .defs
1170 .iter()
1171 .filter(|decl| decl.span.start <= transform_span.start && transform_span.end <= decl.span.end)
1172 .min_by_key(|decl| decl.span.len())
1173 else {
1174 continue;
1175 };
1176 let input_place = node_text(input, src).trim().to_string();
1177 let input_param_index = decl.params.iter().position(|param| param == &input_place);
1178 let output = index
1179 .assignment_values
1180 .iter()
1181 .filter(|assignment| {
1182 assignment.target.is_some()
1183 && assignment.value_span.start <= transform_span.start
1184 && transform_span.end <= assignment.value_span.end
1185 })
1186 .min_by_key(|assignment| assignment.value_span.len())
1187 .and_then(|assignment| assignment.target.clone())
1188 .map_or(
1189 CharacterConstraintOutput::Expression { span: transform_span },
1190 |target| CharacterConstraintOutput::Assignment { target },
1191 );
1192 facts.push(CharacterConstraintFact {
1193 function_span: decl.span,
1194 transform_span,
1195 input_place,
1196 input_param_index,
1197 output,
1198 domain: CharacterConstraintDomain::ExcludesExact { characters },
1199 });
1200 }
1201 facts
1202}
1203
1204fn java_finite_literal_selections(
1205 index: &DeclIndex,
1206 tree: &Tree,
1207 file: FileId,
1208 src: &[u8],
1209) -> Vec<FiniteLiteralSelectionFact> {
1210 if !java_imports_standard_map(tree, file, src) {
1211 return Vec::new();
1212 }
1213 let bindings = java_bindings(tree, src);
1214 if !bindings.bindings.iter().any(|binding| binding.finite_map) {
1215 return Vec::new();
1216 }
1217 let mut selections = Vec::new();
1218 for call in collect_kinds(tree, &["method_invocation"]) {
1219 let Some(object) = call.child_by_field_name("object") else {
1220 continue;
1221 };
1222 let Some(name) = call.child_by_field_name("name") else {
1223 continue;
1224 };
1225 if object.kind() != "identifier" || !matches!(node_text(&name, src).trim(), "get" | "getOrDefault") {
1226 continue;
1227 }
1228 let map_target = node_text(&object, src).trim();
1229 let Some(binding) = bindings.resolve(map_target, object.start_byte(), object.end_byte()) else {
1230 continue;
1231 };
1232 if !binding.finite_map
1233 || (!binding.is_field && binding.initializer.end_byte() > call.start_byte())
1234 || !java_map_selection_has_literal_fallback(call, src)
1235 {
1236 continue;
1237 }
1238 let selection_span = span_of(file, &call);
1239 let Some(fact) = bonsai_lang_api::kit::finite_literal_selection_fact_for_span(
1240 index,
1241 tree,
1242 selection_span,
1243 |value_node| java_expression_is_finite_selection(value_node, call),
1244 ) else {
1245 continue;
1246 };
1247 selections.push(fact);
1248 }
1249 bonsai_lang_api::kit::sort_dedup_finite_literal_selections(&mut selections);
1250 selections
1251}
1252
1253#[derive(Copy, Clone, Debug)]
1254struct JavaBinding<'tree> {
1255 name: &'tree str,
1256 initializer: Node<'tree>,
1257 scope: Node<'tree>,
1258 finite_map: bool,
1259 is_field: bool,
1260 is_static: bool,
1261}
1262
1263struct JavaBindings<'tree> {
1264 bindings: Vec<JavaBinding<'tree>>,
1265 by_name: std::collections::HashMap<String, Vec<usize>>,
1266}
1267
1268impl<'tree> JavaBindings<'tree> {
1269 fn resolve(&self, name: &str, use_start: usize, use_end: usize) -> Option<&JavaBinding<'tree>> {
1270 let candidates = self.by_name.get(name)?;
1271 let smallest_scope = candidates
1272 .iter()
1273 .map(|index| &self.bindings[*index])
1274 .filter(|binding| {
1275 binding.scope.start_byte() <= use_start
1276 && use_end <= binding.scope.end_byte()
1277 && (binding.is_field || binding.initializer.end_byte() <= use_start)
1278 })
1279 .map(|binding| binding.scope.end_byte() - binding.scope.start_byte())
1280 .min()?;
1281 let mut candidates = candidates
1282 .iter()
1283 .map(|index| &self.bindings[*index])
1284 .filter(|binding| {
1285 binding.scope.start_byte() <= use_start
1286 && use_end <= binding.scope.end_byte()
1287 && binding.scope.end_byte() - binding.scope.start_byte() == smallest_scope
1288 && (binding.is_field || binding.initializer.end_byte() <= use_start)
1289 });
1290 let binding = candidates.next()?;
1291 candidates.next().is_none().then_some(binding)
1292 }
1293}
1294
1295fn java_imports_standard_map(tree: &Tree, file: FileId, src: &[u8]) -> bool {
1296 collect_java_imports(tree, file, src)
1297 .iter()
1298 .any(|import| import.module == "java.util.Map" && !import.is_wildcard)
1299}
1300
1301fn java_bindings<'tree>(tree: &'tree Tree, src: &'tree [u8]) -> JavaBindings<'tree> {
1302 let mut bindings = Vec::new();
1303 for declarator in collect_kinds(tree, &["variable_declarator"]) {
1304 let Some(target) = declarator.child_by_field_name("name") else {
1305 continue;
1306 };
1307 if target.kind() != "identifier" {
1308 continue;
1309 }
1310 let Some((scope, declaration, is_field)) = java_binding_scope_and_declaration(declarator) else {
1311 continue;
1312 };
1313 let name = node_text(&target, src).trim();
1314 if name.is_empty() {
1315 continue;
1316 }
1317 let value = declarator.child_by_field_name("value");
1318 bindings.push(JavaBinding {
1319 name,
1320 initializer: value.unwrap_or(target),
1321 scope,
1322 finite_map: java_declaration_has_final_modifier(declaration)
1323 && value.is_some_and(|value| java_is_finite_literal_map(value, src)),
1324 is_field,
1325 is_static: is_field && java_field_has_modifier(declaration, src, "static"),
1326 });
1327 }
1328
1329 for parameter in collect_kinds(
1330 tree,
1331 &["formal_parameter", "spread_parameter", "catch_formal_parameter"],
1332 ) {
1333 let Some(name_node) = parameter
1334 .child_by_field_name("name")
1335 .filter(|name| name.kind() == "identifier")
1336 else {
1337 continue;
1338 };
1339 let mut owner = parameter.parent();
1340 let mut body = None;
1341 while let Some(node) = owner {
1342 if let Some(candidate) = node.child_by_field_name("body") {
1343 body = Some(candidate);
1344 break;
1345 }
1346 if matches!(node.kind(), "class_body" | "program") {
1347 break;
1348 }
1349 owner = node.parent();
1350 }
1351 let Some(scope) = body else {
1352 continue;
1353 };
1354 let name = node_text(&name_node, src).trim();
1355 push_java_blocking_binding(&mut bindings, name_node, scope, name);
1356 }
1357
1358 for lambda in collect_kinds(tree, &["lambda_expression"]) {
1359 let Some(parameters) = lambda.child_by_field_name("parameters") else {
1360 continue;
1361 };
1362 let Some(scope) = lambda.child_by_field_name("body") else {
1363 continue;
1364 };
1365 if parameters.kind() == "identifier" {
1366 let name = node_text(¶meters, src).trim();
1367 push_java_blocking_binding(&mut bindings, parameters, scope, name);
1368 } else if parameters.kind() == "inferred_parameters" {
1369 let mut cursor = parameters.walk();
1370 for name_node in parameters
1371 .named_children(&mut cursor)
1372 .filter(|child| child.kind() == "identifier")
1373 {
1374 let name = node_text(&name_node, src).trim();
1375 push_java_blocking_binding(&mut bindings, name_node, scope, name);
1376 }
1377 }
1378 }
1379
1380 for enhanced_for in collect_kinds(tree, &["enhanced_for_statement"]) {
1381 let Some(name_node) = enhanced_for
1382 .child_by_field_name("name")
1383 .filter(|name| name.kind() == "identifier")
1384 else {
1385 continue;
1386 };
1387 let Some(scope) = enhanced_for.child_by_field_name("body") else {
1388 continue;
1389 };
1390 let name = node_text(&name_node, src).trim();
1391 push_java_blocking_binding(&mut bindings, name_node, scope, name);
1392 }
1393
1394 for pattern in collect_kinds(tree, &["type_pattern", "record_pattern_component"]) {
1395 let mut cursor = pattern.walk();
1396 let Some(name_node) = pattern
1397 .named_children(&mut cursor)
1398 .filter(|child| child.kind() == "identifier")
1399 .last()
1400 else {
1401 continue;
1402 };
1403 let Some(scope) = java_enclosing_block(pattern) else {
1404 continue;
1405 };
1406 let name = node_text(&name_node, src).trim();
1407 push_java_blocking_binding(&mut bindings, name_node, scope, name);
1408 }
1409 let mut by_name: std::collections::HashMap<String, Vec<usize>> = std::collections::HashMap::new();
1410 for (index, binding) in bindings.iter().enumerate() {
1411 by_name.entry(binding.name.to_string()).or_default().push(index);
1412 }
1413 if by_name.contains_key("Map") || java_declares_type_named(tree, src, "Map") {
1414 for binding in &mut bindings {
1415 binding.finite_map = false;
1416 }
1417 }
1418 JavaBindings { bindings, by_name }
1419}
1420
1421fn qualify_java_instance_field_receivers(index: &mut DeclIndex, tree: &Tree, src: &[u8]) {
1428 let Some(current_receiver) = HANDLER
1429 .implicit_receiver_names
1430 .first()
1431 .copied()
1432 .filter(|name| !name.is_empty())
1433 else {
1434 return;
1435 };
1436 let bindings = java_bindings(tree, src);
1437
1438 for fact in &mut index.assignment_values {
1444 let (Some(target_span), Some(target)) = (fact.target_span, fact.target.as_deref()) else {
1445 continue;
1446 };
1447 let Some(target_node) =
1448 bonsai_lang_api::kit::node_at_span(tree.root_node(), target_span, &["identifier"])
1449 .filter(|node| node.kind() == "identifier")
1450 else {
1451 continue;
1452 };
1453 let target_name = node_text(&target_node, src).trim();
1454 if target_name.is_empty() || target_name != target {
1455 continue;
1456 }
1457 let Some(binding) = bindings.resolve(target_name, target_node.start_byte(), target_node.end_byte())
1458 else {
1459 continue;
1460 };
1461 if binding.is_field
1462 && !binding.is_static
1463 && java_enclosing_class_body(target_node).is_some_and(|body| body.id() == binding.scope.id())
1464 {
1465 fact.target = Some(format!("{current_receiver}.{target_name}"));
1466 }
1467 }
1468
1469 let mut rewrites: std::collections::HashMap<Span, (String, String)> = std::collections::HashMap::new();
1470
1471 for fact in &mut index.call_receivers {
1472 if fact.role != bonsai_lang_api::CallReceiverRole::Value {
1473 continue;
1474 }
1475 let Some(receiver_node) =
1476 bonsai_lang_api::kit::node_at_span(tree.root_node(), fact.receiver_span, &["identifier"])
1477 .filter(|node| node.kind() == "identifier")
1478 else {
1479 continue;
1480 };
1481 let receiver_name = node_text(&receiver_node, src).trim();
1482 if receiver_name.is_empty() {
1483 continue;
1484 }
1485 let Some(binding) = bindings.resolve(
1486 receiver_name,
1487 receiver_node.start_byte(),
1488 receiver_node.end_byte(),
1489 ) else {
1490 continue;
1491 };
1492 if !binding.is_field
1496 || binding.is_static
1497 || java_enclosing_class_body(receiver_node).is_none_or(|body| body.id() != binding.scope.id())
1498 {
1499 continue;
1500 }
1501 let qualified = format!("{current_receiver}.{receiver_name}");
1502 fact.value_flow = bonsai_lang_api::ExpressionFlow::from_place(qualified.clone());
1503 rewrites.insert(fact.call_span, (receiver_name.to_string(), qualified));
1504 }
1505
1506 if rewrites.is_empty() {
1507 return;
1508 }
1509 for decl in &mut index.defs {
1510 qualify_java_field_receiver_events(&mut decl.flow_events, &rewrites);
1511 decl.receiver_state_sources = bonsai_lang_api::kit::collect_receiver_state_sources(
1512 &decl.flow_events,
1513 &decl.params,
1514 HANDLER.implicit_receiver_names,
1515 );
1516 }
1517}
1518
1519fn java_enclosing_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
1520 while let Some(parent) = node.parent() {
1521 if parent.kind() == "class_body" {
1522 return Some(parent);
1523 }
1524 node = parent;
1525 }
1526 None
1527}
1528
1529fn qualify_java_field_receiver_events(
1530 events: &mut [FlowEvent],
1531 rewrites: &std::collections::HashMap<Span, (String, String)>,
1532) {
1533 for event in events {
1534 match event {
1535 FlowEvent::Call {
1536 span, name, receiver, ..
1537 } => {
1538 if let Some((unqualified, qualified)) = rewrites.get(span) {
1539 if receiver.as_deref() == Some(unqualified.as_str()) {
1540 *receiver = Some(qualified.clone());
1541 }
1542 qualify_java_receiver_prefix(name, unqualified, qualified);
1543 }
1544 }
1545 FlowEvent::Assign {
1546 span, source_call, ..
1547 } => {
1548 if let Some(source_call) = source_call {
1549 for (call_span, (unqualified, qualified)) in rewrites {
1550 if span.file == call_span.file
1551 && span.start <= call_span.start
1552 && call_span.end <= span.end
1553 {
1554 qualify_java_receiver_prefix(source_call, unqualified, qualified);
1555 }
1556 }
1557 }
1558 }
1559 FlowEvent::Branch {
1560 then_events,
1561 else_events,
1562 ..
1563 } => {
1564 qualify_java_field_receiver_events(then_events, rewrites);
1565 qualify_java_field_receiver_events(else_events, rewrites);
1566 }
1567 FlowEvent::Try {
1568 body,
1569 catch_events,
1570 finally_events,
1571 ..
1572 } => {
1573 qualify_java_field_receiver_events(body, rewrites);
1574 qualify_java_field_receiver_events(catch_events, rewrites);
1575 qualify_java_field_receiver_events(finally_events, rewrites);
1576 }
1577 FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
1578 qualify_java_field_receiver_events(body, rewrites);
1579 }
1580 _ => {}
1581 }
1582 }
1583}
1584
1585fn qualify_java_receiver_prefix(value: &mut String, unqualified: &str, qualified: &str) {
1586 if value == unqualified {
1587 *value = qualified.to_string();
1588 return;
1589 }
1590 if value
1591 .strip_prefix(unqualified)
1592 .is_some_and(|suffix| suffix.starts_with('.'))
1593 {
1594 value.replace_range(..unqualified.len(), qualified);
1595 }
1596}
1597
1598fn push_java_blocking_binding<'tree>(
1599 bindings: &mut Vec<JavaBinding<'tree>>,
1600 name_node: Node<'tree>,
1601 scope: Node<'tree>,
1602 name: &'tree str,
1603) {
1604 if name.is_empty() {
1605 return;
1606 }
1607 bindings.push(JavaBinding {
1608 name,
1609 initializer: name_node,
1610 scope,
1611 finite_map: false,
1612 is_field: false,
1613 is_static: false,
1614 });
1615}
1616
1617fn java_enclosing_block(mut node: Node<'_>) -> Option<Node<'_>> {
1618 while let Some(parent) = node.parent() {
1619 if parent.kind() == "block" {
1620 return Some(parent);
1621 }
1622 node = parent;
1623 }
1624 None
1625}
1626
1627fn java_declares_type_named(tree: &Tree, src: &[u8], wanted: &str) -> bool {
1628 collect_kinds(
1629 tree,
1630 &[
1631 "class_declaration",
1632 "interface_declaration",
1633 "enum_declaration",
1634 "record_declaration",
1635 "annotation_type_declaration",
1636 ],
1637 )
1638 .into_iter()
1639 .any(|declaration| {
1640 declaration
1641 .child_by_field_name("name")
1642 .is_some_and(|name| node_text(&name, src).trim() == wanted)
1643 })
1644}
1645
1646fn java_binding_scope_and_declaration(mut node: Node<'_>) -> Option<(Node<'_>, Node<'_>, bool)> {
1647 let mut declaration = None;
1648 while let Some(parent) = node.parent() {
1649 if matches!(parent.kind(), "field_declaration" | "local_variable_declaration") {
1650 declaration = Some(parent);
1651 }
1652 if matches!(parent.kind(), "block" | "constructor_body") {
1659 return Some((parent, declaration?, false));
1660 }
1661 if parent.kind() == "class_body" {
1662 return Some((parent, declaration?, true));
1663 }
1664 node = parent;
1665 }
1666 None
1667}
1668
1669fn java_declaration_has_final_modifier(declaration: Node<'_>) -> bool {
1670 let Some(modifiers) = declaration
1671 .named_children(&mut declaration.walk())
1672 .find(|child| child.kind() == "modifiers")
1673 else {
1674 return false;
1675 };
1676 modifiers
1677 .children(&mut modifiers.walk())
1678 .any(|modifier| modifier.kind() == "final")
1679}
1680
1681fn java_map_selection_has_literal_fallback(call: Node<'_>, src: &[u8]) -> bool {
1682 let Some(name) = call.child_by_field_name("name") else {
1683 return false;
1684 };
1685 let Some(arguments) = call.child_by_field_name("arguments") else {
1686 return false;
1687 };
1688 let mut cursor = arguments.walk();
1689 let values: Vec<_> = arguments.named_children(&mut cursor).collect();
1690 match node_text(&name, src).trim() {
1691 "get" => values.len() == 1,
1692 "getOrDefault" => values.len() == 2 && java_is_literal_value(values[1], src),
1693 _ => false,
1694 }
1695}
1696
1697fn java_expression_is_finite_selection(mut node: Node<'_>, selection: Node<'_>) -> bool {
1698 while matches!(node.kind(), "parenthesized_expression" | "cast_expression")
1699 && node.named_child_count() >= 1
1700 {
1701 let Some(inner) = node
1702 .child_by_field_name("value")
1703 .or_else(|| node.named_child(u32::try_from(node.named_child_count() - 1).ok()?))
1704 else {
1705 return false;
1706 };
1707 node = inner;
1708 }
1709 node.id() == selection.id()
1710}
1711
1712fn java_is_finite_literal_map(node: Node<'_>, src: &[u8]) -> bool {
1713 if node.kind() != "method_invocation" {
1714 return false;
1715 }
1716 let Some(object) = node.child_by_field_name("object") else {
1717 return false;
1718 };
1719 let Some(name) = node.child_by_field_name("name") else {
1720 return false;
1721 };
1722 if object.kind() != "identifier"
1723 || node_text(&object, src).trim() != "Map"
1724 || node_text(&name, src).trim() != "of"
1725 {
1726 return false;
1727 }
1728 let Some(arguments) = node.child_by_field_name("arguments") else {
1729 return false;
1730 };
1731 let mut cursor = arguments.walk();
1732 let values: Vec<_> = arguments.named_children(&mut cursor).collect();
1733 !values.is_empty()
1734 && values.len() % 2 == 0
1735 && values.iter().all(|value| java_is_literal_value(*value, src))
1736}
1737
1738fn java_is_literal_value(mut node: Node<'_>, src: &[u8]) -> bool {
1739 while matches!(node.kind(), "parenthesized_expression" | "cast_expression")
1740 && node.named_child_count() >= 1
1741 {
1742 let Some(inner) = node
1743 .child_by_field_name("value")
1744 .or_else(|| node.named_child(u32::try_from(node.named_child_count() - 1).unwrap_or(0)))
1745 else {
1746 return false;
1747 };
1748 node = inner;
1749 }
1750 match node.kind() {
1751 "string_literal" => java_static_string_literal(node, src).is_some(),
1752 "character_literal"
1753 | "decimal_integer_literal"
1754 | "hex_integer_literal"
1755 | "octal_integer_literal"
1756 | "binary_integer_literal"
1757 | "decimal_floating_point_literal"
1758 | "hex_floating_point_literal"
1759 | "true"
1760 | "false"
1761 | "null_literal" => true,
1762 "array_initializer" => {
1763 let mut cursor = node.walk();
1764 let is_literal = node
1765 .named_children(&mut cursor)
1766 .all(|child| java_is_literal_value(child, src));
1767 is_literal
1768 }
1769 _ => false,
1770 }
1771}
1772
1773fn populate_java_condition_expressions(
1774 facts: &mut [bonsai_lang_api::BranchConditionFact],
1775 tree: &Tree,
1776 file: FileId,
1777 src: &[u8],
1778) {
1779 for branch in collect_kinds(tree, &["if_statement"]) {
1780 let branch_span = span_of(file, &branch);
1781 let Some(condition) = branch.child_by_field_name("condition") else {
1782 continue;
1783 };
1784 let Some(fact) = facts.iter_mut().find(|fact| fact.branch_span == branch_span) else {
1785 continue;
1786 };
1787 fact.expression = Some(lower_java_condition_expression(condition, file, src));
1788 }
1789}
1790
1791fn lower_java_condition_expression(node: Node<'_>, file: FileId, src: &[u8]) -> ConditionExpressionFact {
1792 if node.kind() == "parenthesized_expression" {
1793 if let Some(inner) = node.named_child(0) {
1794 return lower_java_condition_expression(inner, file, src);
1795 }
1796 }
1797 let span = span_of(file, &node);
1798 if node.kind() == "unary_expression" {
1799 if let Some(operand) = node
1800 .child_by_field_name("operand")
1801 .or_else(|| node.named_child(0))
1802 {
1803 let operator = src
1804 .get(node.start_byte()..operand.start_byte())
1805 .and_then(|bytes| std::str::from_utf8(bytes).ok())
1806 .map(str::trim);
1807 if operator == Some("!") {
1808 return ConditionExpressionFact::Not {
1809 span,
1810 operand: Box::new(lower_java_condition_expression(operand, file, src)),
1811 };
1812 }
1813 }
1814 }
1815 if node.kind() == "binary_expression" {
1816 if let (Some(left), Some(right)) = (
1817 node.child_by_field_name("left"),
1818 node.child_by_field_name("right"),
1819 ) {
1820 let operator = src
1821 .get(left.end_byte()..right.start_byte())
1822 .and_then(|bytes| std::str::from_utf8(bytes).ok())
1823 .map(str::trim);
1824 match operator {
1825 Some("||") => {
1826 return merge_java_condition_junction(
1827 span,
1828 lower_java_condition_expression(left, file, src),
1829 lower_java_condition_expression(right, file, src),
1830 false,
1831 );
1832 }
1833 Some("&&") => {
1834 return merge_java_condition_junction(
1835 span,
1836 lower_java_condition_expression(left, file, src),
1837 lower_java_condition_expression(right, file, src),
1838 true,
1839 );
1840 }
1841 Some("==" | "!=") => {
1842 return ConditionExpressionFact::Equality {
1843 span,
1844 relation: if operator == Some("==") {
1845 ConditionEquality::Equal
1846 } else {
1847 ConditionEquality::NotEqual
1848 },
1849 left: java_condition_operand(left, file, src),
1850 right: java_condition_operand(right, file, src),
1851 };
1852 }
1853 _ => {}
1854 }
1855 }
1856 }
1857 if node.kind() == "instanceof_expression" {
1858 if let (Some(subject), Some(type_node)) = (
1859 node.child_by_field_name("left")
1860 .or_else(|| node.child_by_field_name("expression"))
1861 .or_else(|| node.named_child(0)),
1862 node.child_by_field_name("right")
1863 .or_else(|| node.child_by_field_name("type"))
1864 .or_else(|| node.named_child(1)),
1865 ) {
1866 let type_name = node_text(&type_node, src).trim().to_string();
1867 if !type_name.is_empty() {
1868 return ConditionExpressionFact::TypeTest {
1869 span,
1870 subject: java_condition_operand(subject, file, src),
1871 type_name,
1872 };
1873 }
1874 }
1875 }
1876 ConditionExpressionFact::Atom { span }
1877}
1878
1879fn merge_java_condition_junction(
1880 span: Span,
1881 left: ConditionExpressionFact,
1882 right: ConditionExpressionFact,
1883 all: bool,
1884) -> ConditionExpressionFact {
1885 let mut operands = Vec::new();
1886 let mut push = |operand: ConditionExpressionFact| match (all, operand) {
1887 (true, ConditionExpressionFact::All { operands: nested, .. })
1888 | (false, ConditionExpressionFact::Any { operands: nested, .. }) => operands.extend(nested),
1889 (_, operand) => operands.push(operand),
1890 };
1891 push(left);
1892 push(right);
1893 if all {
1894 ConditionExpressionFact::All { span, operands }
1895 } else {
1896 ConditionExpressionFact::Any { span, operands }
1897 }
1898}
1899
1900fn java_condition_operand(node: Node<'_>, file: FileId, src: &[u8]) -> ConditionOperandFact {
1901 ConditionOperandFact {
1902 span: span_of(file, &node),
1903 value_flow: bonsai_lang_api::kit::expression_flow_from_node_with_handler(node, file, src, &HANDLER),
1904 static_string: java_static_string_literal(node, src),
1905 static_value: java_static_scalar(node, src),
1906 }
1907}
1908
1909fn java_static_string_literal(node: Node<'_>, src: &[u8]) -> Option<String> {
1910 if node.kind() != "string_literal" {
1911 return None;
1912 }
1913 let text = node_text(&node, src);
1914 let inner = text.strip_prefix('"')?.strip_suffix('"')?;
1915 decode_java_string_literal(inner)
1916}
1917
1918fn decode_java_string_literal(inner: &str) -> Option<String> {
1925 let mut input = inner.chars().peekable();
1926 let mut decoded = String::with_capacity(inner.len());
1927 while let Some(character) = input.next() {
1928 if character != '\\' {
1929 decoded.push(character);
1930 continue;
1931 }
1932 let escape = input.next()?;
1933 match escape {
1934 'b' => decoded.push('\u{0008}'),
1935 't' => decoded.push('\t'),
1936 'n' => decoded.push('\n'),
1937 'f' => decoded.push('\u{000c}'),
1938 'r' => decoded.push('\r'),
1939 's' => decoded.push(' '),
1940 '"' => decoded.push('"'),
1941 '\'' => decoded.push('\''),
1942 '\\' => decoded.push('\\'),
1943 'u' => {
1944 while input.peek() == Some(&'u') {
1945 input.next();
1946 }
1947 let mut value = 0_u32;
1948 for _ in 0..4 {
1949 value = value.checked_mul(16)? + input.next()?.to_digit(16)?;
1950 }
1951 let scalar = char::from_u32(value)?;
1952 if matches!(scalar, '\\' | '"' | '\r' | '\n') {
1957 return None;
1958 }
1959 decoded.push(scalar);
1960 }
1961 first @ '0'..='7' => {
1962 let mut value = first.to_digit(8)?;
1963 let mut digits = 1;
1964 while digits < 3 {
1965 let Some(next) = input.peek().copied() else {
1966 break;
1967 };
1968 let Some(digit) = next.to_digit(8) else {
1969 break;
1970 };
1971 if digits == 2 && first > '3' {
1972 break;
1973 }
1974 input.next();
1975 value = value.checked_mul(8)? + digit;
1976 digits += 1;
1977 }
1978 decoded.push(char::from_u32(value)?);
1979 }
1980 _ => return None,
1981 }
1982 }
1983 Some(decoded)
1984}
1985
1986fn java_static_scalar(node: Node<'_>, src: &[u8]) -> Option<StaticScalarValue> {
1987 match node.kind() {
1988 "string_literal" => Some(StaticScalarValue::String(java_static_string_literal(node, src)?)),
1989 "true" => Some(StaticScalarValue::Boolean(true)),
1990 "false" => Some(StaticScalarValue::Boolean(false)),
1991 "null_literal" => Some(StaticScalarValue::Null),
1992 _ => None,
1993 }
1994}
1995
1996fn java_string_compositions(tree: &Tree, file: FileId, src: &[u8]) -> Vec<StringCompositionFact> {
1997 let mut facts = Vec::new();
1998 for declarator in collect_kinds(tree, &["variable_declarator"]) {
1999 let (Some(name), Some(value)) = (
2000 declarator.child_by_field_name("name"),
2001 declarator.child_by_field_name("value"),
2002 ) else {
2003 continue;
2004 };
2005 if name.kind() != "identifier" {
2006 continue;
2007 }
2008 let mut parts = Vec::new();
2009 if java_lower_string_composition(value, file, src, &mut parts) && parts.len() > 1 {
2010 facts.push(StringCompositionFact {
2011 container_span: span_of(file, &declarator),
2012 value_span: span_of(file, &value),
2013 target: Some(node_text(&name, src).trim().to_string()),
2014 parts,
2015 });
2016 }
2017 }
2018 facts.sort_by_key(|fact| (fact.container_span.start, fact.container_span.end));
2019 facts.dedup();
2020 facts
2021}
2022
2023fn java_lower_string_composition(
2024 mut node: Node<'_>,
2025 file: FileId,
2026 src: &[u8],
2027 out: &mut Vec<StringCompositionPart>,
2028) -> bool {
2029 while node.kind() == "parenthesized_expression" && node.named_child_count() == 1 {
2030 let Some(inner) = node.named_child(0) else {
2031 return false;
2032 };
2033 node = inner;
2034 }
2035 if let Some(value) = java_static_string_literal(node, src) {
2036 out.push(StringCompositionPart::Literal { value });
2037 return true;
2038 }
2039 if node.kind() == "method_invocation" {
2040 let Some(name) = node.child_by_field_name("name") else {
2041 return false;
2042 };
2043 out.push(StringCompositionPart::Call {
2044 span: span_of(file, &name),
2045 });
2046 return true;
2047 }
2048 if node.kind() == "binary_expression" {
2049 let (Some(left), Some(right)) = (
2050 node.child_by_field_name("left"),
2051 node.child_by_field_name("right"),
2052 ) else {
2053 return false;
2054 };
2055 let operator = src
2056 .get(left.end_byte()..right.start_byte())
2057 .and_then(|bytes| std::str::from_utf8(bytes).ok())
2058 .map(str::trim);
2059 return operator == Some("+")
2060 && java_lower_string_composition(left, file, src, out)
2061 && java_lower_string_composition(right, file, src, out);
2062 }
2063 if node.kind() == "ternary_expression" {
2064 let condition = node
2065 .child_by_field_name("condition")
2066 .or_else(|| node.named_child(0));
2067 let consequence = node
2068 .child_by_field_name("consequence")
2069 .or_else(|| node.named_child(1));
2070 let alternative = node
2071 .child_by_field_name("alternative")
2072 .or_else(|| node.named_child(2));
2073 let (Some(condition), Some(consequence), Some(alternative)) = (condition, consequence, alternative)
2074 else {
2075 return false;
2076 };
2077 let Some(condition_call) = java_null_equality_call(condition, src) else {
2078 return false;
2079 };
2080 let (call, fallback) = if alternative.kind() == "method_invocation" {
2081 (alternative, java_static_string_literal(consequence, src))
2082 } else if consequence.kind() == "method_invocation" {
2083 (consequence, java_static_string_literal(alternative, src))
2084 } else {
2085 return false;
2086 };
2087 let Some(fallback) = fallback else {
2088 return false;
2089 };
2090 if java_method_call_identity(condition_call, src) != java_method_call_identity(call, src) {
2091 return false;
2092 }
2093 out.push(StringCompositionPart::CallOrLiteral {
2094 span: span_of(file, &call.child_by_field_name("name").unwrap_or(call)),
2095 fallback,
2096 });
2097 return true;
2098 }
2099 false
2100}
2101
2102fn java_null_equality_call<'tree>(node: Node<'tree>, src: &[u8]) -> Option<Node<'tree>> {
2103 if node.kind() != "binary_expression" {
2104 return None;
2105 }
2106 let left = node.child_by_field_name("left")?;
2107 let right = node.child_by_field_name("right")?;
2108 let operator = src
2109 .get(left.end_byte()..right.start_byte())
2110 .and_then(|bytes| std::str::from_utf8(bytes).ok())
2111 .map(str::trim);
2112 if operator != Some("==") {
2113 return None;
2114 }
2115 if left.kind() == "method_invocation" && right.kind() == "null_literal" {
2116 Some(left)
2117 } else if right.kind() == "method_invocation" && left.kind() == "null_literal" {
2118 Some(right)
2119 } else {
2120 None
2121 }
2122}
2123
2124fn java_method_call_identity(node: Node<'_>, src: &[u8]) -> Option<(String, String, usize)> {
2125 if node.kind() != "method_invocation" {
2126 return None;
2127 }
2128 let receiver = node.child_by_field_name("object")?;
2129 let name = node.child_by_field_name("name")?;
2130 let arguments = node.child_by_field_name("arguments")?;
2131 let mut cursor = arguments.walk();
2132 Some((
2133 node_text(&receiver, src).trim().to_string(),
2134 node_text(&name, src).trim().to_string(),
2135 arguments.named_children(&mut cursor).count(),
2136 ))
2137}
2138
2139fn populate_java_static_scalar_facts(index: &mut DeclIndex, tree: &Tree, file: FileId, src: &[u8]) {
2140 let static_values: std::collections::HashMap<_, _> =
2141 collect_kinds(tree, &["string_literal", "true", "false", "null_literal"])
2142 .into_iter()
2143 .filter_map(|node| {
2144 let value = java_static_scalar(node, src)?;
2145 let span = span_of(file, &node);
2146 Some(((span.start, span.end), value))
2147 })
2148 .collect();
2149 let call_values: std::collections::HashMap<_, _> =
2150 collect_kinds(tree, &["method_invocation", "object_creation_expression"])
2151 .into_iter()
2152 .map(|node| {
2153 let span = span_of(file, &node);
2154 ((span.start, span.end), node)
2155 })
2156 .collect();
2157 for fact in &mut index.assignment_values {
2158 if fact.direct_call_name.is_none() {
2159 continue;
2160 }
2161 let Some(call) = call_values.get(&(fact.value_span.start, fact.value_span.end)) else {
2162 continue;
2163 };
2164 let Some(arguments) = call.child_by_field_name("arguments") else {
2165 continue;
2166 };
2167 let mut cursor = arguments.walk();
2168 let argument_nodes: Vec<_> = arguments.named_children(&mut cursor).collect();
2169 if argument_nodes.is_empty() {
2170 continue;
2171 }
2172 let values: Option<Vec<_>> = argument_nodes
2173 .into_iter()
2174 .map(|argument| java_static_scalar(argument, src))
2175 .collect();
2176 fact.exact_static_call_args = values;
2177 }
2178 for fact in &mut index.call_receivers {
2179 fact.static_value = static_values
2180 .get(&(fact.receiver_span.start, fact.receiver_span.end))
2181 .cloned();
2182 }
2183 bonsai_lang_api::kit::populate_call_argument_static_values(
2184 index,
2185 tree,
2186 file,
2187 src,
2188 &HANDLER,
2189 java_static_scalar,
2190 );
2191 populate_java_array_argument_sequences(index, tree, file, src);
2192}
2193
2194fn populate_java_array_argument_sequences(index: &mut DeclIndex, tree: &Tree, file: FileId, src: &[u8]) {
2199 let arrays: std::collections::HashMap<_, _> = collect_kinds(tree, &["array_creation_expression"])
2200 .into_iter()
2201 .map(|node| {
2202 let span = span_of(file, &node);
2203 ((span.start, span.end), node)
2204 })
2205 .collect();
2206 for fact in &mut index.call_argument_values {
2207 let Some(array) = arrays.get(&(fact.argument_span.start, fact.argument_span.end)) else {
2208 continue;
2209 };
2210 let mut array_cursor = array.walk();
2211 let Some(initializer) = array.child_by_field_name("value").or_else(|| {
2212 array
2213 .named_children(&mut array_cursor)
2214 .find(|child| child.kind() == "array_initializer")
2215 }) else {
2216 continue;
2217 };
2218 let mut cursor = initializer.walk();
2219 let values = initializer
2220 .named_children(&mut cursor)
2221 .map(|value| java_static_scalar(value, src))
2222 .collect::<Vec<_>>();
2223 if !values.is_empty() {
2224 fact.exact_static_sequence_values = Some(values);
2225 }
2226 }
2227}
2228
2229fn collect_java_method_type_aliases(
2232 tree: &Tree,
2233 file: FileId,
2234 src: &[u8],
2235 field_aliases: &[TypeAliasBinding],
2236) -> Vec<(bonsai_common::Span, Vec<TypeAliasBinding>)> {
2237 let mut aliases_by_method = Vec::new();
2238 for method_node in collect_kinds(tree, &["method_declaration", "constructor_declaration"]) {
2239 let mut method_aliases = field_aliases.to_vec();
2242 method_aliases.extend(collect_java_type_aliases(
2243 method_node,
2244 src,
2245 &[
2246 "formal_parameter",
2247 "local_variable_declaration",
2248 "enhanced_for_statement",
2249 "resource",
2254 ],
2255 ));
2256 let method_type_bounds = java_type_parameter_bounds(method_node, src);
2257 expand_java_type_parameter_aliases(&mut method_aliases, &method_type_bounds);
2258 dedup_type_aliases(&mut method_aliases);
2259 aliases_by_method.push((span_of(file, &method_node), method_aliases));
2260 }
2261 aliases_by_method
2262}
2263
2264type JavaTypeParameterBounds = Vec<(String, Vec<String>)>;
2265
2266fn collect_java_class_type_parameter_bounds(
2267 tree: &Tree,
2268 file: FileId,
2269 src: &[u8],
2270) -> Vec<(Span, JavaTypeParameterBounds)> {
2271 collect_kinds(
2272 tree,
2273 &[
2274 "class_declaration",
2275 "interface_declaration",
2276 "record_declaration",
2277 "enum_declaration",
2278 "annotation_type_declaration",
2279 ],
2280 )
2281 .into_iter()
2282 .filter_map(|node| {
2283 let bounds = java_type_parameter_bounds(node, src);
2284 (!bounds.is_empty()).then(|| (span_of(file, &node), bounds))
2285 })
2286 .collect()
2287}
2288
2289fn java_type_parameter_bounds(node: Node<'_>, src: &[u8]) -> JavaTypeParameterBounds {
2290 let Some(parameters) = node.child_by_field_name("type_parameters") else {
2291 return Vec::new();
2292 };
2293 let mut out = Vec::new();
2294 let mut parameter_cursor = parameters.walk();
2295 for parameter in parameters.named_children(&mut parameter_cursor) {
2296 if parameter.kind() != "type_parameter" {
2297 continue;
2298 }
2299 let mut child_cursor = parameter.walk();
2300 let children = parameter.named_children(&mut child_cursor).collect::<Vec<_>>();
2301 let Some(name) = children
2302 .iter()
2303 .find(|child| child.kind() == "type_identifier")
2304 .map(|child| node_text(child, src).trim().to_string())
2305 .filter(|name| !name.is_empty())
2306 else {
2307 continue;
2308 };
2309 let mut bounds = Vec::new();
2310 for bound in children.iter().filter(|child| child.kind() == "type_bound") {
2311 let mut bound_cursor = bound.walk();
2312 for bound_type in bound.named_children(&mut bound_cursor) {
2313 let text = node_text(&bound_type, src).trim();
2314 if !text.is_empty() && !bounds.iter().any(|existing| existing == text) {
2315 bounds.push(text.to_string());
2316 }
2317 }
2318 }
2319 if !bounds.is_empty() {
2320 out.push((name, bounds));
2321 }
2322 }
2323 out
2324}
2325
2326fn expand_java_type_parameter_aliases(aliases: &mut Vec<TypeAliasBinding>, bounds: &JavaTypeParameterBounds) {
2327 if aliases.is_empty() || bounds.is_empty() {
2328 return;
2329 }
2330 loop {
2331 let before = aliases.len();
2332 let current = aliases.clone();
2333 for alias in current {
2334 let alias_type = canonical_java_type_name(&alias.type_name)
2335 .unwrap_or_else(|| alias.type_name.trim().to_string());
2336 for (_, upper_bounds) in bounds.iter().filter(|(parameter, _)| parameter == &alias_type) {
2337 for upper_bound in upper_bounds {
2338 if let Some(canonical) = canonical_java_type_name(upper_bound) {
2339 let qualified = qualified_java_type_name(upper_bound);
2340 push_java_type_alias(aliases, &alias.name, &canonical, qualified.as_deref());
2341 }
2342 }
2343 }
2344 }
2345 dedup_type_aliases(aliases);
2346 if aliases.len() == before {
2347 break;
2348 }
2349 }
2350}
2351
2352fn attach_java_nested_callable_type_aliases(index: &mut DeclIndex, tree: &Tree, file: FileId, src: &[u8]) {
2358 let mut callable_bindings = Vec::new();
2359 for declaration in collect_kinds(tree, &["local_variable_declaration"]) {
2360 let Some(type_node) = declaration.child_by_field_name("type") else {
2361 continue;
2362 };
2363 let type_text = node_text(&type_node, src);
2364 let Some(canonical_type) = canonical_java_type_name(type_text) else {
2365 continue;
2366 };
2367 let qualified_type = qualified_java_type_name(type_text);
2368 let mut cursor = declaration.walk();
2369 for declarator in declaration
2370 .named_children(&mut cursor)
2371 .filter(|child| child.kind() == "variable_declarator")
2372 {
2373 let (Some(name_node), Some(value)) = (
2374 declarator.child_by_field_name("name"),
2375 declarator.child_by_field_name("value"),
2376 ) else {
2377 continue;
2378 };
2379 if value.kind() != "lambda_expression" {
2380 continue;
2381 }
2382 let name = node_text(&name_node, src).trim();
2383 if name.is_empty() {
2384 continue;
2385 }
2386 let mut aliases = Vec::new();
2387 push_java_type_alias(&mut aliases, name, &canonical_type, qualified_type.as_deref());
2388 callable_bindings.push((span_of(file, &value), aliases));
2389 }
2390 }
2391
2392 let inherited = index
2393 .defs
2394 .iter()
2395 .map(|decl| (decl.symbol, decl.type_aliases.clone()))
2396 .collect::<std::collections::HashMap<_, _>>();
2397 for decl in &mut index.defs {
2398 if let Some(parent_aliases) = decl.parent.and_then(|parent| inherited.get(&parent)) {
2399 decl.type_aliases.extend(parent_aliases.iter().cloned());
2400 }
2401 if let Some(aliases) = callable_bindings
2402 .iter()
2403 .find_map(|(span, aliases)| (*span == decl.span).then_some(aliases))
2404 {
2405 decl.type_aliases.extend(aliases.iter().cloned());
2406 }
2407 dedup_type_aliases(&mut decl.type_aliases);
2408 }
2409}
2410
2411fn collect_java_type_aliases(root: Node<'_>, src: &[u8], kinds: &[&str]) -> Vec<TypeAliasBinding> {
2414 let mut aliases = Vec::new();
2415 let mut work_stack = vec![root];
2416 while let Some(node) = work_stack.pop() {
2417 if kinds.contains(&node.kind()) {
2418 aliases.extend(java_type_aliases_from_decl(node, src));
2419 }
2420 let mut cursor = node.walk();
2421 for child in node.named_children(&mut cursor) {
2422 work_stack.push(child);
2423 }
2424 }
2425 expand_java_platform_supertypes(&mut aliases);
2426 dedup_type_aliases(&mut aliases);
2427 aliases
2428}
2429
2430fn java_type_aliases_from_decl(node: Node<'_>, src: &[u8]) -> Vec<TypeAliasBinding> {
2434 let Some(type_node) = node.child_by_field_name("type") else {
2435 return Vec::new();
2436 };
2437 let type_text = node_text(&type_node, src);
2438 let mut aliases = Vec::new();
2439 if let Some(canonical_type) = canonical_java_type_name(type_text) {
2440 let qualified_type = qualified_java_type_name(type_text);
2441 if let Some(name_node) = node.child_by_field_name("name") {
2443 push_java_type_alias(
2444 &mut aliases,
2445 node_text(&name_node, src),
2446 &canonical_type,
2447 qualified_type.as_deref(),
2448 );
2449 return aliases;
2450 }
2451 let mut cursor = node.walk();
2453 for child in node.named_children(&mut cursor) {
2454 if child.kind() != "variable_declarator" {
2455 continue;
2456 }
2457 if let Some(name_node) = child.child_by_field_name("name") {
2458 let name = node_text(&name_node, src);
2459 push_java_type_alias(&mut aliases, name, &canonical_type, qualified_type.as_deref());
2460 }
2461 }
2462 return aliases;
2463 }
2464 if type_text.trim() == "var" {
2469 let mut cursor = node.walk();
2470 for child in node.named_children(&mut cursor) {
2471 if child.kind() != "variable_declarator" {
2472 continue;
2473 }
2474 let Some(name_node) = child.child_by_field_name("name") else {
2475 continue;
2476 };
2477 let Some(value) = child.child_by_field_name("value") else {
2478 continue;
2479 };
2480 if let Some(cast_raw) = java_cast_type_of_init(value, src) {
2481 if let Some(canonical) = canonical_java_type_name(&cast_raw) {
2482 let qualified = qualified_java_type_name(&cast_raw);
2483 push_java_type_alias(
2484 &mut aliases,
2485 node_text(&name_node, src),
2486 &canonical,
2487 qualified.as_deref(),
2488 );
2489 }
2490 }
2491 }
2492 }
2493 aliases
2494}
2495
2496fn java_cast_type_of_init(init: Node<'_>, src: &[u8]) -> Option<String> {
2501 let mut n = init;
2502 while n.kind() == "parenthesized_expression" {
2503 let mut cursor = n.walk();
2504 n = n.named_children(&mut cursor).next()?;
2505 }
2506 if n.kind() == "cast_expression" {
2507 return n
2508 .child_by_field_name("type")
2509 .map(|t| node_text(&t, src).to_string());
2510 }
2511 None
2512}
2513
2514fn canonical_java_type_name(raw: &str) -> Option<String> {
2519 let without_generics = raw.split('<').next().unwrap_or(raw);
2521 let without_arrays = without_generics.split('[').next().unwrap_or(without_generics);
2522 let bare_type = without_arrays
2524 .trim()
2525 .rsplit('.')
2526 .next()
2527 .unwrap_or(without_arrays)
2528 .trim();
2529 if bare_type.is_empty() || bare_type == "var" {
2530 return None;
2531 }
2532 Some(bare_type.to_string())
2533}
2534
2535fn qualified_java_type_name(raw: &str) -> Option<String> {
2541 let without_generics = raw.split('<').next().unwrap_or(raw);
2542 let without_arrays = without_generics.split('[').next().unwrap_or(without_generics);
2543 let qualified = without_arrays.trim();
2544 let mut parts = qualified.split('.').filter(|part| !part.is_empty()).peekable();
2545 parts.peek()?;
2546 let segments: Vec<&str> = parts.collect();
2547 if segments.len() < 2 {
2548 return None;
2549 }
2550 let tail = segments.last()?.trim();
2551 if tail.is_empty() {
2552 return None;
2553 }
2554 Some(qualified.to_string())
2555}
2556
2557fn push_java_type_alias(
2558 aliases: &mut Vec<TypeAliasBinding>,
2559 name: &str,
2560 canonical_type: &str,
2561 qualified_type: Option<&str>,
2562) {
2563 if let Some(qualified_type) = qualified_type.filter(|qualified| *qualified != canonical_type) {
2564 push_type_alias(aliases, name, qualified_type);
2565 }
2566 push_type_alias(aliases, name, canonical_type);
2567}
2568
2569fn push_type_alias(aliases: &mut Vec<TypeAliasBinding>, name: &str, type_name: &str) {
2572 let bare_name = name.trim();
2573 if bare_name.is_empty() || bare_name == type_name {
2574 return;
2575 }
2576 aliases.push(TypeAliasBinding {
2577 name: bare_name.to_string(),
2578 type_name: type_name.to_string(),
2579 });
2580}
2581
2582fn expand_java_platform_supertypes(aliases: &mut Vec<TypeAliasBinding>) {
2583 let original = aliases.clone();
2584 for alias in original {
2585 for supertype in java_platform_supertypes(&alias.type_name) {
2586 push_type_alias(aliases, &alias.name, supertype);
2587 }
2588 }
2589}
2590
2591fn java_platform_supertypes(type_name: &str) -> &'static [&'static str] {
2592 match type_name {
2593 "CallableStatement" => &["PreparedStatement", "Statement"],
2594 "PreparedStatement" => &["Statement"],
2595 "Statement" => &[],
2596 "ArrayList" | "LinkedList" | "Vector" => &["List", "Collection", "Iterable"],
2597 "HashSet" | "LinkedHashSet" | "TreeSet" => &["Set", "Collection", "Iterable"],
2598 "HashMap" | "LinkedHashMap" | "TreeMap" => &["Map"],
2599 "List" | "Set" => &["Collection", "Iterable"],
2600 "Collection" => &["Iterable"],
2601 _ => &[],
2602 }
2603}
2604
2605fn dedup_type_aliases(aliases: &mut Vec<TypeAliasBinding>) {
2607 let mut deduped = Vec::new();
2608 for alias in aliases.drain(..) {
2609 if !deduped.contains(&alias) {
2610 deduped.push(alias);
2611 }
2612 }
2613 *aliases = deduped;
2614}
2615
2616fn collect_java_imports(tree: &Tree, file: FileId, src: &[u8]) -> Vec<ImportSpec> {
2617 let mut imports: Vec<_> = collect_kinds(tree, &["import_declaration"])
2618 .into_iter()
2619 .filter_map(|import| java_import_spec(import, file, src))
2620 .collect();
2621 imports.extend(
2622 collect_kinds(tree, &["scoped_type_identifier"])
2623 .into_iter()
2624 .filter(|type_use| !has_ancestor_kind(*type_use, "import_declaration"))
2625 .filter(|type_use| {
2626 type_use
2627 .parent()
2628 .is_none_or(|parent| parent.kind() != "scoped_type_identifier")
2629 })
2630 .filter_map(|type_use| java_qualified_type_package_spec(type_use, file, src)),
2631 );
2632 imports.sort_by_key(|import| import.span.start);
2633 imports.dedup_by(|left, right| left.span == right.span && left.module == right.module);
2634 imports
2635}
2636
2637fn java_qualified_type_package_spec(type_use: Node<'_>, file: FileId, src: &[u8]) -> Option<ImportSpec> {
2643 let module = node_text(&type_use, src).trim();
2644 let alias = import_tail_binding(module)?;
2645 Some(ImportSpec {
2646 span: span_of(file, &type_use),
2647 module: module.to_string(),
2648 alias: Some(alias),
2649 is_wildcard: false,
2650 original_name: None,
2651 scope: ImportScope::Local,
2652 })
2653}
2654
2655fn has_ancestor_kind(mut node: Node<'_>, kind: &str) -> bool {
2656 while let Some(parent) = node.parent() {
2657 if parent.kind() == kind {
2658 return true;
2659 }
2660 node = parent;
2661 }
2662 false
2663}
2664
2665fn java_import_spec(import: Node<'_>, file: FileId, src: &[u8]) -> Option<ImportSpec> {
2666 let is_static = import
2667 .children(&mut import.walk())
2668 .any(|child| child.kind() == "static");
2669 let mut named_cursor = import.walk();
2670 let named_children: Vec<_> = import.named_children(&mut named_cursor).collect();
2671 let is_wildcard = named_children.iter().any(|child| child.kind() == "asterisk");
2672 let path_node = named_children
2673 .iter()
2674 .find(|child| matches!(child.kind(), "identifier" | "scoped_identifier"))?;
2675 let full_path = node_text(path_node, src).trim();
2676 if full_path.is_empty() {
2677 return None;
2678 }
2679 let (module, alias, original_name) = if is_static && !is_wildcard {
2680 let (owner, member) = full_path.rsplit_once('.')?;
2681 (
2682 owner.to_string(),
2683 Some(member.to_string()),
2684 Some(member.to_string()),
2685 )
2686 } else {
2687 (
2688 full_path.to_string(),
2689 (!is_wildcard).then(|| import_tail_binding(full_path)).flatten(),
2690 None,
2691 )
2692 };
2693 Some(ImportSpec {
2694 span: span_of(file, &import),
2695 module,
2696 alias,
2697 is_wildcard,
2698 original_name,
2699 scope: ImportScope::Module,
2700 })
2701}
2702
2703fn import_tail_binding(module: &str) -> Option<String> {
2704 let tail = module
2705 .rsplit_once('.')
2706 .map(|(_, tail)| tail)
2707 .unwrap_or(module)
2708 .trim();
2709 (!tail.is_empty() && tail != module).then(|| tail.to_string())
2710}
2711
2712fn collect_java_visibility(
2720 root: Node<'_>,
2721 file: FileId,
2722 src: &[u8],
2723) -> std::collections::HashMap<bonsai_common::Span, Visibility> {
2724 let mut visibility_by_span = std::collections::HashMap::new();
2725 let mut work_stack = vec![root];
2726 while let Some(node) = work_stack.pop() {
2727 let kind = node.kind();
2728 let is_class_or_member_decl = matches!(
2729 kind,
2730 "method_declaration"
2731 | "constructor_declaration"
2732 | "class_declaration"
2733 | "interface_declaration"
2734 | "enum_declaration"
2735 | "annotation_type_declaration"
2736 | "record_declaration"
2737 );
2738 if is_class_or_member_decl {
2739 visibility_by_span.insert(span_of(file, &node), java_node_visibility(&node, src));
2740 }
2741 let mut cursor = node.walk();
2744 for child in node.children(&mut cursor) {
2745 work_stack.push(child);
2746 }
2747 }
2748 visibility_by_span
2749}
2750
2751fn java_node_visibility(node: &Node<'_>, src: &[u8]) -> Visibility {
2754 let mut cursor = node.walk();
2755 for child in node.children(&mut cursor) {
2756 if child.kind() != "modifiers" {
2757 continue;
2758 }
2759 let mut modifier_cursor = child.walk();
2760 for modifier in child.children(&mut modifier_cursor) {
2761 match node_text(&modifier, src) {
2762 "public" => return Visibility::Public,
2763 "private" => return Visibility::Private,
2764 "protected" => return Visibility::Protected,
2765 _ => {}
2766 }
2767 }
2768 }
2769 Visibility::Module
2772}
2773
2774fn is_class_like(kind: DeclKind) -> bool {
2783 matches!(
2784 kind,
2785 DeclKind::Class | DeclKind::Interface | DeclKind::Trait | DeclKind::Struct | DeclKind::Enum
2786 )
2787}
2788
2789fn rewrite_java_explicit_constructor_invocations(index: &mut DeclIndex) {
2790 use std::collections::HashMap;
2791
2792 let class_info: HashMap<bonsai_common::SymbolId, (String, Vec<String>)> = index
2793 .defs
2794 .iter()
2795 .filter(|decl| is_class_like(decl.kind))
2796 .map(|decl| (decl.symbol, (decl.name.clone(), decl.bases.clone())))
2797 .collect();
2798
2799 for decl in &mut index.defs {
2800 if !matches!(decl.kind, DeclKind::Constructor) {
2801 continue;
2802 }
2803 let Some(parent) = decl.parent else {
2804 continue;
2805 };
2806 let Some((class_name, bases)) = class_info.get(&parent) else {
2807 continue;
2808 };
2809 let this_ctor = class_name.as_str();
2810 let super_ctor = bases.first().map(String::as_str);
2811 rewrite_java_explicit_constructor_invocations_in_events(&mut decl.flow_events, this_ctor, super_ctor);
2812 }
2813}
2814
2815fn rewrite_java_explicit_constructor_invocations_in_events(
2816 events: &mut [FlowEvent],
2817 this_ctor: &str,
2818 super_ctor: Option<&str>,
2819) {
2820 for event in events {
2821 match event {
2822 FlowEvent::Call {
2823 name,
2824 receiver,
2825 receiver_types,
2826 call_kind,
2827 ..
2828 } => {
2829 let replacement = match name.trim() {
2830 "this" => Some((this_ctor, "this")),
2831 "super" => super_ctor.map(|ctor| (ctor, "super")),
2832 _ => None,
2833 };
2834 if let Some((replacement, replacement_receiver)) =
2835 replacement.filter(|(replacement, _)| !replacement.is_empty())
2836 {
2837 name.clear();
2838 name.push_str(replacement);
2839 *receiver = Some(replacement_receiver.to_string());
2840 receiver_types.clear();
2841 *call_kind = bonsai_lang_api::CallKind::Constructor;
2849 }
2850 }
2851 FlowEvent::Branch {
2852 then_events,
2853 else_events,
2854 ..
2855 } => {
2856 rewrite_java_explicit_constructor_invocations_in_events(then_events, this_ctor, super_ctor);
2857 rewrite_java_explicit_constructor_invocations_in_events(else_events, this_ctor, super_ctor);
2858 }
2859 FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
2860 rewrite_java_explicit_constructor_invocations_in_events(body, this_ctor, super_ctor);
2861 }
2862 FlowEvent::Try {
2863 body,
2864 catch_events,
2865 finally_events,
2866 ..
2867 } => {
2868 rewrite_java_explicit_constructor_invocations_in_events(body, this_ctor, super_ctor);
2869 rewrite_java_explicit_constructor_invocations_in_events(catch_events, this_ctor, super_ctor);
2870 rewrite_java_explicit_constructor_invocations_in_events(
2871 finally_events,
2872 this_ctor,
2873 super_ctor,
2874 );
2875 }
2876 FlowEvent::Assign { .. }
2877 | FlowEvent::AggregateAssign { .. }
2878 | FlowEvent::Return { .. }
2879 | FlowEvent::Throw { .. }
2880 | FlowEvent::Break { .. }
2881 | FlowEvent::Continue { .. }
2882 | FlowEvent::Yield { .. }
2883 | FlowEvent::Await { .. }
2884 | FlowEvent::Lifecycle { .. } => {}
2885 }
2886 }
2887}
2888
2889fn collect_java_class_bases(
2899 tree: &Tree,
2900 file: FileId,
2901 src: &[u8],
2902) -> Vec<(bonsai_common::Span, Vec<String>)> {
2903 let mut out = Vec::new();
2904 let class_kinds = &[
2905 "class_declaration",
2906 "interface_declaration",
2907 "record_declaration",
2908 "enum_declaration",
2909 "annotation_type_declaration",
2910 ];
2911 for class_node in collect_kinds(tree, class_kinds) {
2912 let mut bases: Vec<String> = Vec::new();
2913 if let Some(sc) = class_node.child_by_field_name("superclass") {
2915 collect_java_base_names(sc, src, &mut bases);
2916 }
2917 if let Some(ifaces) = class_node.child_by_field_name("interfaces") {
2919 collect_java_base_names(ifaces, src, &mut bases);
2920 }
2921 if let Some(extends) = class_node.child_by_field_name("extends_interfaces") {
2923 collect_java_base_names(extends, src, &mut bases);
2924 }
2925 if let Some(permits) = class_node.child_by_field_name("permits") {
2934 collect_java_base_names(permits, src, &mut bases);
2935 }
2936 if !bases.is_empty() {
2937 out.push((span_of(file, &class_node), bases));
2938 }
2939 }
2940 out
2941}
2942
2943fn collect_java_class_string_constants(
2944 tree: &Tree,
2945 file: FileId,
2946 src: &[u8],
2947) -> Vec<(bonsai_common::Span, Vec<FlowEvent>)> {
2948 let class_kinds = &[
2949 "class_declaration",
2950 "interface_declaration",
2951 "record_declaration",
2952 "enum_declaration",
2953 "annotation_type_declaration",
2954 ];
2955 let mut out = Vec::new();
2956 for class_node in collect_kinds(tree, class_kinds) {
2957 let Some(body) = class_node.child_by_field_name("body") else {
2958 continue;
2959 };
2960 let mut events = Vec::new();
2961 let mut cursor = body.walk();
2962 for child in body.named_children(&mut cursor) {
2963 if child.kind() == "field_declaration" {
2964 collect_java_final_string_field_assigns(child, file, src, &mut events);
2965 }
2966 }
2967 if !events.is_empty() {
2968 out.push((span_of(file, &class_node), events));
2969 }
2970 }
2971 out
2972}
2973
2974fn collect_java_final_string_field_assigns(
2975 field: Node<'_>,
2976 file: FileId,
2977 src: &[u8],
2978 out: &mut Vec<FlowEvent>,
2979) {
2980 if !java_field_has_modifier(field, src, "final") || !java_field_type_is_string(field, src) {
2981 return;
2982 }
2983 let mut cursor = field.walk();
2984 for child in field.named_children(&mut cursor) {
2985 if child.kind() != "variable_declarator" {
2986 continue;
2987 }
2988 let Some(name_node) = child.child_by_field_name("name") else {
2989 continue;
2990 };
2991 let Some(value_node) = child.child_by_field_name("value") else {
2992 continue;
2993 };
2994 if value_node.kind() != "string_literal" {
2995 continue;
2996 }
2997 out.push(FlowEvent::Assign {
2998 span: span_of(file, &child),
2999 target: node_text(&name_node, src).trim().to_string(),
3000 source_name: None,
3001 source_call: None,
3002 source_call_args: Vec::new(),
3003 source_names: Vec::new(),
3004 declares_new_binding: false,
3005 value_kind: Some(AssignValueKind::Literal),
3006 });
3007 }
3008}
3009
3010fn java_field_has_modifier(field: Node<'_>, src: &[u8], wanted: &str) -> bool {
3011 let mut cursor = field.walk();
3012 for child in field.named_children(&mut cursor) {
3013 if child.kind() != "modifiers" {
3014 continue;
3015 }
3016 if node_text(&child, src)
3017 .split_ascii_whitespace()
3018 .any(|modifier| modifier == wanted)
3019 {
3020 return true;
3021 }
3022 }
3023 false
3024}
3025
3026fn java_field_type_is_string(field: Node<'_>, src: &[u8]) -> bool {
3027 let Some(type_node) = field.child_by_field_name("type") else {
3028 return false;
3029 };
3030 matches!(
3031 canonical_java_type_name(node_text(&type_node, src)).as_deref(),
3032 Some("String")
3033 )
3034}
3035
3036fn attach_java_class_string_constants(
3037 index: &mut DeclIndex,
3038 constants_by_class: &[(bonsai_common::Span, Vec<FlowEvent>)],
3039) {
3040 if constants_by_class.is_empty() {
3041 return;
3042 }
3043 let parent_by_symbol: std::collections::HashMap<_, _> = index
3044 .defs
3045 .iter()
3046 .filter_map(|decl| Some((decl.symbol, decl.parent?)))
3047 .collect();
3048 let class_symbol_by_span: std::collections::HashMap<_, _> = index
3049 .defs
3050 .iter()
3051 .filter(|decl| is_class_like(decl.kind))
3052 .map(|decl| (decl.span, decl.symbol))
3053 .collect();
3054 let constants_by_symbol: std::collections::HashMap<_, _> = constants_by_class
3055 .iter()
3056 .filter_map(|(span, events)| {
3057 class_symbol_by_span
3058 .get(span)
3059 .copied()
3060 .map(|symbol| (symbol, events))
3061 })
3062 .collect();
3063
3064 for decl in &mut index.defs {
3065 if !matches!(
3066 decl.kind,
3067 DeclKind::Function | DeclKind::Method | DeclKind::Constructor
3068 ) {
3069 continue;
3070 }
3071 let mut ancestors = Vec::new();
3072 let mut parent = decl.parent;
3073 while let Some(symbol) = parent {
3074 ancestors.push(symbol);
3075 parent = parent_by_symbol.get(&symbol).copied();
3076 }
3077 if ancestors.is_empty() {
3078 continue;
3079 }
3080
3081 let mut visible_constants = Vec::new();
3082 for symbol in ancestors.into_iter().rev() {
3083 if let Some(events) = constants_by_symbol.get(&symbol) {
3084 visible_constants.extend((*events).iter().cloned());
3085 }
3086 }
3087 visible_constants.retain(|event| {
3088 let FlowEvent::Assign { target, .. } = event else {
3089 return false;
3090 };
3091 !decl.params.iter().any(|param| param == target)
3092 && !decl
3093 .flow_events
3094 .iter()
3095 .any(|event| flow_event_assigns_target(event, target))
3096 });
3097 if !visible_constants.is_empty() {
3098 visible_constants.extend(std::mem::take(&mut decl.flow_events));
3099 decl.flow_events = visible_constants;
3100 }
3101 }
3102}
3103
3104fn flow_event_assigns_target(event: &FlowEvent, wanted: &str) -> bool {
3105 match event {
3106 FlowEvent::Assign { target, .. } => target == wanted,
3107 FlowEvent::Branch {
3108 then_events,
3109 else_events,
3110 ..
3111 } => {
3112 then_events
3113 .iter()
3114 .any(|event| flow_event_assigns_target(event, wanted))
3115 || else_events
3116 .iter()
3117 .any(|event| flow_event_assigns_target(event, wanted))
3118 }
3119 FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
3120 body.iter().any(|event| flow_event_assigns_target(event, wanted))
3121 }
3122 FlowEvent::Try {
3123 body,
3124 catch_events,
3125 finally_events,
3126 ..
3127 } => {
3128 body.iter().any(|event| flow_event_assigns_target(event, wanted))
3129 || catch_events
3130 .iter()
3131 .any(|event| flow_event_assigns_target(event, wanted))
3132 || finally_events
3133 .iter()
3134 .any(|event| flow_event_assigns_target(event, wanted))
3135 }
3136 _ => false,
3137 }
3138}
3139
3140fn collect_java_base_names(node: Node<'_>, src: &[u8], out: &mut Vec<String>) {
3145 let mut stack = vec![node];
3146 while let Some(n) = stack.pop() {
3147 match n.kind() {
3148 "type_identifier" | "scoped_type_identifier" | "generic_type" => {
3149 if let Some(name) = canonical_java_type_name(node_text(&n, src)) {
3150 if !out.iter().any(|b| b == &name) {
3151 out.push(name);
3152 }
3153 }
3154 }
3155 _ => {}
3156 }
3157 let mut cursor = n.walk();
3158 for child in n.named_children(&mut cursor) {
3159 stack.push(child);
3160 }
3161 }
3162}
3163
3164fn populate_java_exception_types(events: &mut [bonsai_lang_api::FlowEvent], tree: &Tree, src: &[u8]) {
3172 use bonsai_lang_api::FlowEvent;
3173 for event in events {
3174 match event {
3175 FlowEvent::Throw {
3176 span, thrown_type, ..
3177 } => {
3178 if thrown_type.is_some() {
3179 continue;
3180 }
3181 if let Some(node) =
3182 bonsai_lang_api::kit::node_at_span(tree.root_node(), *span, &["throw_statement"])
3183 {
3184 if let Some(name) = java_thrown_type_for_node(node, src) {
3185 *thrown_type = Some(name);
3186 }
3187 }
3188 }
3189 FlowEvent::Try {
3190 span,
3191 body,
3192 catch_events,
3193 finally_events,
3194 catch_types,
3195 catch_param,
3196 ..
3197 } => {
3198 if let Some(node) =
3199 bonsai_lang_api::kit::node_at_span(tree.root_node(), *span, &["try_statement"])
3200 {
3201 if catch_types.is_empty() {
3202 *catch_types = collect_java_catch_types(node, src);
3203 }
3204 if let Some(name) = collect_java_catch_param_name(node, src) {
3209 *catch_param = Some(name);
3210 }
3211 }
3212 populate_java_exception_types(body, tree, src);
3213 populate_java_exception_types(catch_events, tree, src);
3214 populate_java_exception_types(finally_events, tree, src);
3215 }
3216 FlowEvent::Branch {
3217 then_events,
3218 else_events,
3219 ..
3220 } => {
3221 populate_java_exception_types(then_events, tree, src);
3222 populate_java_exception_types(else_events, tree, src);
3223 }
3224 FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
3225 populate_java_exception_types(body, tree, src);
3226 }
3227 _ => {}
3228 }
3229 }
3230}
3231
3232fn java_thrown_type_for_node(throw_node: Node<'_>, src: &[u8]) -> Option<String> {
3233 let mut cursor = throw_node.walk();
3235 for child in throw_node.named_children(&mut cursor) {
3236 if child.kind() == "object_creation_expression" {
3237 if let Some(t) = child.child_by_field_name("type") {
3238 return Some(bonsai_lang_api::kit::canonical_simple_type_name(node_text(
3239 &t, src,
3240 )));
3241 }
3242 let mut tcur = child.walk();
3244 for descendant in child.named_children(&mut tcur) {
3245 if matches!(
3246 descendant.kind(),
3247 "type_identifier" | "generic_type" | "scoped_type_identifier"
3248 ) {
3249 return Some(bonsai_lang_api::kit::canonical_simple_type_name(node_text(
3250 &descendant,
3251 src,
3252 )));
3253 }
3254 }
3255 }
3256 }
3257 None
3258}
3259
3260fn collect_java_catch_param_name(try_node: Node<'_>, src: &[u8]) -> Option<String> {
3261 let mut cursor = try_node.walk();
3270 for child in try_node.named_children(&mut cursor) {
3271 if child.kind() != "catch_clause" {
3272 continue;
3273 }
3274 let mut ccur = child.walk();
3275 for sub in child.named_children(&mut ccur) {
3276 if sub.kind() != "catch_formal_parameter" {
3277 continue;
3278 }
3279 if let Some(n) = sub.child_by_field_name("name") {
3280 return Some(node_text(&n, src).trim().to_string());
3281 }
3282 let mut pcur = sub.walk();
3284 let mut last_ident: Option<Node<'_>> = None;
3285 for ptype in sub.named_children(&mut pcur) {
3286 if ptype.kind() == "identifier" {
3287 last_ident = Some(ptype);
3288 }
3289 }
3290 if let Some(n) = last_ident {
3291 return Some(node_text(&n, src).trim().to_string());
3292 }
3293 }
3294 }
3295 None
3296}
3297
3298fn collect_java_catch_types(try_node: Node<'_>, src: &[u8]) -> Vec<String> {
3299 let mut out: Vec<String> = Vec::new();
3300 let mut cursor = try_node.walk();
3301 for child in try_node.named_children(&mut cursor) {
3302 if child.kind() != "catch_clause" {
3303 continue;
3304 }
3305 let mut ccur = child.walk();
3307 for sub in child.named_children(&mut ccur) {
3308 if sub.kind() != "catch_formal_parameter" {
3309 continue;
3310 }
3311 let mut pcur = sub.walk();
3312 for ptype in sub.named_children(&mut pcur) {
3313 if ptype.kind() == "catch_type" {
3314 let mut tcur = ptype.walk();
3315 for t in ptype.named_children(&mut tcur) {
3316 if matches!(
3317 t.kind(),
3318 "type_identifier" | "generic_type" | "scoped_type_identifier"
3319 ) {
3320 let name = bonsai_lang_api::kit::canonical_simple_type_name(node_text(&t, src));
3321 if !name.is_empty() && !out.iter().any(|x| x == &name) {
3322 out.push(name);
3323 }
3324 }
3325 }
3326 }
3327 }
3328 }
3329 }
3330 out
3331}
3332
3333fn rewrite_java_reflection_chain(events: &mut [bonsai_lang_api::FlowEvent]) {
3348 use bonsai_lang_api::FlowEvent;
3349 use std::collections::HashMap;
3350 let mut reflective_alias: HashMap<String, String> = HashMap::new();
3354 for event in events.iter_mut() {
3355 match event {
3356 FlowEvent::Assign {
3357 target,
3358 source_call,
3359 source_call_args,
3360 ..
3361 } => {
3362 let Some(callee) = source_call else { continue };
3365 let Some(literal_arg) = source_call_args.first() else {
3366 continue;
3367 };
3368 let Some(literal_text) = strip_java_string_quotes(literal_arg) else {
3369 continue;
3370 };
3371 let is_for_name = callee == "Class.forName" || callee.ends_with(".forName");
3373 if is_for_name {
3374 reflective_alias.insert(target.clone(), literal_text);
3375 continue;
3376 }
3377 if let Some(get_method_receiver) = callee.strip_suffix(".getMethod") {
3381 if let Some(class_name) = reflective_alias.get(get_method_receiver) {
3382 let chained = format!("{class_name}.{literal_text}");
3383 reflective_alias.insert(target.clone(), chained);
3384 }
3385 }
3386 }
3387 FlowEvent::Call {
3388 name, receiver, args, ..
3389 } => {
3390 let Some(receiver_name) = receiver.as_deref() else {
3394 continue;
3395 };
3396 if !name.ends_with(".invoke") {
3397 continue;
3398 }
3399 let Some(target_class_method) = reflective_alias.get(receiver_name) else {
3400 continue;
3401 };
3402 name.clone_from(target_class_method);
3404 if !args.is_empty() {
3409 args.remove(0);
3410 }
3411 *receiver = target_class_method
3415 .rsplit_once('.')
3416 .map(|(class_part, _)| class_part.to_string());
3417 }
3418 FlowEvent::Branch {
3421 then_events,
3422 else_events,
3423 ..
3424 } => {
3425 rewrite_java_reflection_chain(then_events);
3426 rewrite_java_reflection_chain(else_events);
3427 }
3428 FlowEvent::Loop { body, .. } | FlowEvent::Defer { body, .. } | FlowEvent::Using { body, .. } => {
3429 rewrite_java_reflection_chain(body);
3430 }
3431 FlowEvent::Try {
3432 body,
3433 catch_events,
3434 finally_events,
3435 ..
3436 } => {
3437 rewrite_java_reflection_chain(body);
3438 rewrite_java_reflection_chain(catch_events);
3439 rewrite_java_reflection_chain(finally_events);
3440 }
3441 _ => {}
3442 }
3443 }
3444}
3445
3446fn strip_java_string_quotes(text: &str) -> Option<String> {
3450 let trimmed = text.trim();
3451 if trimmed.starts_with('"') && trimmed.ends_with('"') && trimmed.len() >= 2 {
3452 return Some(trimmed[1..trimmed.len() - 1].to_string());
3453 }
3454 None
3455}
3456
3457fn extract_java_package(root: Node<'_>, src: &[u8]) -> Option<Vec<String>> {
3458 let mut cursor = root.walk();
3459 for child in root.children(&mut cursor) {
3460 if child.kind() != "package_declaration" {
3461 continue;
3462 }
3463 let mut sub = child.walk();
3466 for subchild in child.children(&mut sub) {
3467 if matches!(subchild.kind(), "scoped_identifier" | "identifier") {
3468 let text = node_text(&subchild, src);
3469 let segments: Vec<String> = text
3470 .split('.')
3471 .map(str::trim)
3472 .filter(|s| !s.is_empty())
3473 .map(str::to_string)
3474 .collect();
3475 if !segments.is_empty() {
3476 return Some(segments);
3477 }
3478 }
3479 }
3480 }
3481 None
3482}