use crate::symbols::{
Confidence, OccurrenceRole, SymbolId, SymbolOccurrence, SymbolProvenance, resolve,
};
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use tree_sitter::{Node, Tree};
const MAX_USAGES_PER_FILE: usize = 2000;
const MAX_UNRESOLVED_PER_FILE: usize = 512;
const CANDIDATE_BLOCKLIST: &[&str] = &["self", "super", "Self", "init", "_"];
const CALL_PARENT_KINDS: &[&str] = &[
"call_expression", "call_suffix", "message_expression", ];
pub(super) fn collect_usages(
tree: &Tree,
content: &str,
relative: &str,
defined: &HashMap<String, SymbolId>,
definition_name_ranges: &[(usize, usize)],
) -> Vec<SymbolOccurrence> {
let mut out = Vec::new();
let src = content.as_bytes();
let mut candidate_seen: HashSet<(String, OccurrenceRole)> = HashSet::new();
let definition_name_ranges: HashSet<(usize, usize)> =
definition_name_ranges.iter().copied().collect();
let mut stack = vec![tree.root_node()];
while let Some(node) = stack.pop() {
if out.len() >= MAX_USAGES_PER_FILE {
break;
}
if is_identifier(node) {
let range = (node.start_byte(), node.end_byte());
if !definition_name_ranges.contains(&range)
&& let Ok(text) = node.utf8_text(src)
{
let role = occurrence_role(node);
if let Some(symbol_id) = defined.get(text) {
out.push(SymbolOccurrence {
symbol_id: symbol_id.clone(),
file: PathBuf::from(relative),
range: super::symbols::text_range(node),
role,
confidence: Confidence::Heuristic,
engine: SymbolProvenance::TreeSitter,
});
} else if text.len() >= 2
&& !CANDIDATE_BLOCKLIST.contains(&text)
&& candidate_seen.len() < MAX_UNRESOLVED_PER_FILE
&& candidate_seen.insert((text.to_string(), role))
{
out.push(SymbolOccurrence {
symbol_id: resolve::unresolved_id(text),
file: PathBuf::from(relative),
range: super::symbols::text_range(node),
role,
confidence: Confidence::Heuristic,
engine: SymbolProvenance::TreeSitter,
});
}
}
continue;
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.is_named() {
stack.push(child);
}
}
}
out
}
fn is_identifier(node: Node) -> bool {
matches!(
node.kind(),
"identifier"
| "simple_identifier"
| "type_identifier"
| "field_identifier"
| "method_identifier"
)
}
fn occurrence_role(node: Node) -> OccurrenceRole {
let mut cur = node.parent();
let mut hops = 0;
while let Some(p) = cur {
if hops >= 4 {
break;
}
if CALL_PARENT_KINDS.contains(&p.kind()) {
return OccurrenceRole::Call;
}
cur = p.parent();
hops += 1;
}
OccurrenceRole::Reference
}