use super::languages::{java, javascript, kotlin, python, typescript};
use crate::models::{EntityKind, ParsedEntity, ReferenceIntent};
use tree_sitter::Node;
#[expect(
clippy::too_many_arguments,
reason = "function is verbose but correct — extraction deferred"
)]
pub(crate) fn collect_orphaned_references(
root: Node<'_>,
source: &[u8],
lang_name: &str,
entities: &mut Vec<ParsedEntity>,
covered_ranges: &[(usize, usize)],
file_path: &str,
repo_name: &str,
) {
let mut all_intents: Vec<(ReferenceIntent, usize)> = Vec::new();
collect_all_reference_intents_with_byte_pos(root, source, lang_name, &mut all_intents);
let orphaned_intents: Vec<ReferenceIntent> = all_intents
.into_iter()
.filter(|(_, byte_pos)| {
!covered_ranges
.iter()
.any(|(start, end)| byte_pos >= start && byte_pos < end)
})
.map(|(intent, _)| intent)
.collect();
if orphaned_intents.is_empty() {
return;
}
let module_kind = match lang_name {
"python" => EntityKind::PythonModule,
"rust" => EntityKind::RustModule,
_ => EntityKind::Function,
};
let mut module_entity = ParsedEntity::new(
"<module>",
module_kind,
"<module>",
None,
None,
lang_name,
file_path,
1,
usize::MAX,
None,
repo_name,
);
if entities.is_empty() {
module_entity.reference_intents = orphaned_intents;
entities.push(module_entity);
return;
}
entities.push(module_entity);
for intent in orphaned_intents {
let orphan_line = match &intent {
ReferenceIntent::Call { line, .. } => *line,
ReferenceIntent::Extends { line, .. } => *line,
ReferenceIntent::Implements { line, .. } => *line,
ReferenceIntent::TypeReference { line, .. } => *line,
ReferenceIntent::ValueReference { line, .. } => *line,
ReferenceIntent::DomElementReference { line, .. } => *line,
ReferenceIntent::CssClassUsage { line, .. } => *line,
ReferenceIntent::HtmlFileImport { line, .. } => *line,
ReferenceIntent::CssFileImport { line, .. } => *line,
ReferenceIntent::RustMacroCall { line, .. } => *line,
ReferenceIntent::VclSubCall { line, .. } => *line,
ReferenceIntent::VclBackendRef { line, .. } => *line,
ReferenceIntent::VclProbeRef { line, .. } => *line,
ReferenceIntent::VclAclRef { line, .. } => *line,
ReferenceIntent::VclInclude { line, .. } => *line,
ReferenceIntent::VclVmodImport { line, .. } => *line,
ReferenceIntent::VclUnusedRef { line, .. } => *line,
};
let target_idx = find_nearest_entity_by_line(entities, orphan_line);
entities[target_idx].reference_intents.push(intent);
}
}
pub(crate) fn collect_all_reference_intents_with_byte_pos(
node: Node<'_>,
source: &[u8],
lang_name: &str,
intents: &mut Vec<(ReferenceIntent, usize)>,
) {
if lang_name == "typescript" {
typescript::collect_all_reference_intents_typescript(node, source, intents);
} else if lang_name == "java" {
java::collect_all_reference_intents_java(node, source, intents);
} else if lang_name == "javascript" {
javascript::collect_all_reference_intents_javascript(node, source, intents);
} else if lang_name == "python" {
let mut python_call_intents = Vec::new();
python::extract_call_intents_python(node, source, &mut python_call_intents);
for call in python_call_intents {
intents.push((
ReferenceIntent::Call {
method: call.method,
receiver: call.receiver,
line: call.line,
arg_count: call.arg_count,
},
0, ));
}
let mut python_import_intents = Vec::new();
python::extract_import_intents_python(node, source, &mut python_import_intents);
for intent in python_import_intents {
intents.push((intent, node.start_byte()));
}
let mut python_value_intents = Vec::new();
python::extract_value_references_python(node, source, &mut python_value_intents);
for intent in python_value_intents {
intents.push((intent, node.start_byte()));
}
} else if lang_name == "c" || lang_name == "cpp" {
let mut cpp_call_intents = Vec::new();
crate::pipeline::parser::languages::cpp::extract_call_intents_cpp(
node,
source,
&mut cpp_call_intents,
);
for call in cpp_call_intents {
intents.push((
ReferenceIntent::Call {
method: call.method,
receiver: call.receiver,
line: call.line,
arg_count: call.arg_count,
},
0, ));
}
} else if lang_name == "kotlin" {
kotlin::collect_all_reference_intents_kotlin(node, source, intents);
}
}
pub(crate) fn find_nearest_entity_by_line(entities: &[ParsedEntity], target_line: usize) -> usize {
let mut best_idx = 0;
let mut best_distance = usize::MAX;
for (idx, entity) in entities.iter().enumerate() {
if target_line >= entity.start_line && target_line <= entity.end_line {
let range_size = entity.end_line - entity.start_line;
if range_size < best_distance {
best_distance = range_size;
best_idx = idx;
}
}
}
if best_distance == usize::MAX {
for (idx, entity) in entities.iter().enumerate() {
let entity_line = entity.start_line;
if entity_line <= target_line {
let distance = target_line - entity_line;
if distance < best_distance {
best_distance = distance;
best_idx = idx;
}
}
}
}
if best_distance == usize::MAX {
for (idx, entity) in entities.iter().enumerate() {
let distance = entity.start_line.abs_diff(target_line);
if distance < best_distance {
best_distance = distance;
best_idx = idx;
}
}
}
best_idx
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::ParsedEntity;
#[test]
fn test_find_nearest_entity_by_line_before() {
let entity1 = ParsedEntity::new(
"Entity1",
EntityKind::Class,
"Entity1",
None,
None,
"java",
"/test.java",
10,
20,
None,
"test-repo",
);
let entity2 = ParsedEntity::new(
"Entity2",
EntityKind::Method,
"Entity2",
None,
None,
"java",
"/test.java",
30,
40,
None,
"test-repo",
);
let entity3 = ParsedEntity::new(
"Entity3",
EntityKind::Function,
"Entity3",
None,
None,
"java",
"/test.java",
50,
60,
None,
"test-repo",
);
let entities = vec![entity1, entity2, entity3];
let idx = find_nearest_entity_by_line(&entities, 25);
assert_eq!(idx, 0, "Line 25 should be assigned to entity1 (line 10)");
}
#[test]
fn test_find_nearest_entity_by_line_exact() {
let entity1 = ParsedEntity::new(
"Entity1",
EntityKind::Class,
"Entity1",
None,
None,
"java",
"/test.java",
10,
20,
None,
"test-repo",
);
let entities = vec![entity1];
let idx = find_nearest_entity_by_line(&entities, 10);
assert_eq!(idx, 0);
}
#[test]
fn test_find_nearest_entity_by_line_after() {
let entity1 = ParsedEntity::new(
"Entity1",
EntityKind::Class,
"Entity1",
None,
None,
"java",
"/test.java",
10,
20,
None,
"test-repo",
);
let entity2 = ParsedEntity::new(
"Entity2",
EntityKind::Method,
"Entity2",
None,
None,
"java",
"/test.java",
30,
40,
None,
"test-repo",
);
let entities = vec![entity1, entity2];
let idx = find_nearest_entity_by_line(&entities, 50);
assert_eq!(idx, 1);
}
#[test]
fn test_find_nearest_entity_by_line_before_all() {
let entity1 = ParsedEntity::new(
"Entity1",
EntityKind::Class,
"Entity1",
None,
None,
"java",
"/test.java",
10,
20,
None,
"test-repo",
);
let entity2 = ParsedEntity::new(
"Entity2",
EntityKind::Method,
"Entity2",
None,
None,
"java",
"/test.java",
30,
40,
None,
"test-repo",
);
let entities = vec![entity1, entity2];
let idx = find_nearest_entity_by_line(&entities, 5);
assert_eq!(idx, 0);
}
#[test]
fn test_find_nearest_entity_containment_preferred() {
let entity1 = ParsedEntity::new(
"Entity1",
EntityKind::Class,
"Entity1",
None,
None,
"java",
"/test.java",
10,
20,
None,
"test-repo",
);
let entity2 = ParsedEntity::new(
"Entity2",
EntityKind::Method,
"Entity2",
None,
None,
"java",
"/test.java",
30,
40,
None,
"test-repo",
);
let entities = vec![entity1, entity2];
let idx = find_nearest_entity_by_line(&entities, 15);
assert_eq!(
idx, 0,
"Line 15 should be assigned to entity1 (contained in 10-20)"
);
}
#[test]
fn test_find_nearest_entity_containment_chooses_smallest_range() {
let entity1 = ParsedEntity::new(
"Entity1",
EntityKind::Class,
"Entity1",
None,
None,
"java",
"/test.java",
5,
50,
None,
"test-repo",
);
let entity2 = ParsedEntity::new(
"Entity2",
EntityKind::Method,
"Entity2",
None,
None,
"java",
"/test.java",
20,
30,
None,
"test-repo",
);
let entities = vec![entity1, entity2];
let idx = find_nearest_entity_by_line(&entities, 25);
assert_eq!(
idx, 1,
"Line 25 should be assigned to entity2 (smallest containing range)"
);
}
}