use std::collections::HashMap;
use crate::types::{Language, NodeKind, ReferenceKind, UnresolvedReference};
use super::helpers::{capitalize, chain_language, chain_shape, resolved_by_name};
use super::{ReferenceResolver, ResolutionContext, ResolutionResult, ResolutionStats};
use super::{ResolvedRef, UnresolvedRef};
use crate::resolution::import_resolver::{
is_php_include_path_ref, resolve_jvm_import, resolve_via_import,
};
use crate::resolution::name_matcher::{
crosses_known_family, match_function_ref, match_reference, same_language_family,
};
impl<'db> ReferenceResolver<'db> {
pub fn resolve_all(
&mut self,
unresolved_refs: &[UnresolvedReference],
mut on_progress: Option<&mut dyn FnMut(usize, usize)>,
) -> ResolutionResult {
self.warm_caches();
let refs = unresolved_refs
.iter()
.map(|reference| UnresolvedRef {
from_node_id: reference.from_node_id.clone(),
reference_name: reference.reference_name.clone(),
reference_kind: reference.reference_kind,
line: reference.line,
column: reference.column,
file_path: reference
.file_path
.clone()
.unwrap_or_else(|| self.get_file_path_from_node_id(&reference.from_node_id)),
language: reference
.language
.unwrap_or_else(|| self.get_language_from_node_id(&reference.from_node_id)),
candidates: reference.candidates.clone(),
})
.collect::<Vec<_>>();
let mut resolved = Vec::new();
let mut unresolved = Vec::new();
let mut by_method: HashMap<String, u64> = HashMap::new();
let total = refs.len();
let mut last_reported_percent = -1i64;
for (idx, reference) in refs.into_iter().enumerate() {
if let Some(result) = self.resolve_one(&reference) {
*by_method
.entry(resolved_by_name(result.resolved_by).to_string())
.or_default() += 1;
resolved.push(result);
} else {
unresolved.push(reference);
}
if let Some(on_progress) = on_progress.as_deref_mut()
&& total > 0
{
let current_percent = ((idx * 100) / total) as i64;
if current_percent > last_reported_percent {
last_reported_percent = current_percent;
on_progress(idx + 1, total);
}
}
}
if let Some(on_progress) = on_progress
&& total > 0
{
on_progress(total, total);
}
ResolutionResult {
stats: ResolutionStats {
total: total as u64,
resolved: resolved.len() as u64,
unresolved: unresolved.len() as u64,
by_method,
},
resolved,
unresolved,
}
}
pub(super) fn has_any_possible_match(&mut self, name: &str) -> bool {
let Some(known_names) = &self.known_names else {
return true;
};
if known_names.contains(name) {
return true;
}
if let Some(dot_idx) = name.find('.') {
let receiver = &name[..dot_idx];
let member = &name[dot_idx + 1..];
if known_names.contains(receiver) || known_names.contains(member) {
return true;
}
let capitalized = capitalize(receiver);
if known_names.contains(&capitalized) {
return true;
}
if let Some(last_dot) = name.rfind('.').filter(|idx| *idx > dot_idx) {
let tail = &name[last_dot + 1..];
if !tail.is_empty() && known_names.contains(tail) {
return true;
}
}
}
if let Some(colon_idx) = name.find("::") {
let receiver = &name[..colon_idx];
let member = &name[colon_idx + 2..];
if known_names.contains(receiver) || known_names.contains(member) {
return true;
}
if let Some(last_colon) = name.rfind("::").filter(|idx| *idx > colon_idx) {
let tail = &name[last_colon + 2..];
if !tail.is_empty() && known_names.contains(tail) {
return true;
}
}
}
if let Some(file_name) = name.rsplit('/').next().filter(|file| *file != name)
&& known_names.contains(file_name)
{
return true;
}
false
}
fn matches_any_import(&mut self, reference: &UnresolvedRef) -> bool {
self.get_import_mappings(&reference.file_path, reference.language)
.into_iter()
.any(|imp| {
imp.local_name == reference.reference_name
|| reference
.reference_name
.starts_with(&format!("{}.", imp.local_name))
|| reference
.reference_name
.starts_with(&format!("{}::", imp.local_name))
})
}
pub fn resolve_one(&mut self, reference: &UnresolvedRef) -> Option<ResolvedRef> {
if self.is_built_in_or_external(reference) {
return None;
}
if !self.has_any_possible_match(&reference.reference_name)
&& !self.matches_any_import(reference)
&& !self
.frameworks
.iter()
.any(|framework| framework.claims_reference(&reference.reference_name))
{
return None;
}
if reference.reference_kind == ReferenceKind::FunctionRef {
if reference.reference_name.starts_with("this.") {
let result = self.resolve_this_member_fn_ref(reference);
return self.gate_language(result, reference);
}
let via_import_raw = resolve_via_import(reference, self);
let via_import = self.gate_language(via_import_raw, reference);
if let Some(via_import) = via_import
&& let Some(target) = self
.queries
.get_node_by_id(&via_import.target_node_id)
.unwrap_or(None)
&& matches!(target.kind, NodeKind::Function | NodeKind::Method)
{
return Some(via_import);
}
let result = match_function_ref(reference, self);
return self.gate_language(result, reference);
}
if let Some(jvm_import) = resolve_jvm_import(reference, self) {
return Some(jvm_import);
}
if reference.language == Language::Razor
&& let Some(razor_result) = self.resolve_razor_using(reference)
{
return Some(razor_result);
}
let mut candidates = Vec::new();
let import_result_raw = resolve_via_import(reference, self);
if let Some(import_result) = self.gate_language(import_result_raw, reference) {
if import_result.confidence >= 0.9 {
return Some(import_result);
}
candidates.push(import_result);
}
if is_php_include_path_ref(reference) {
return candidates
.into_iter()
.max_by(|a, b| a.confidence.partial_cmp(&b.confidence).unwrap());
}
let name_result_raw = match_reference(reference, self);
if let Some(name_result) = self.gate_language(name_result_raw, reference) {
candidates.push(name_result);
}
if candidates.is_empty() {
if reference.reference_kind == ReferenceKind::Calls
&& chain_language(reference.language)
&& chain_shape(&reference.reference_name)
{
self.deferred_chain_refs.push(reference.clone());
}
return None;
}
candidates
.into_iter()
.max_by(|a, b| a.confidence.partial_cmp(&b.confidence).unwrap())
}
pub(super) fn gate_language(
&mut self,
result: Option<ResolvedRef>,
reference: &UnresolvedRef,
) -> Option<ResolvedRef> {
let result = result?;
let target_language = self.get_language_from_node_id(&result.target_node_id);
if matches!(target_language, Language::Unknown) {
return Some(result);
}
if matches!(
reference.reference_kind,
ReferenceKind::References
| ReferenceKind::FunctionRef
| ReferenceKind::TypeOf
| ReferenceKind::Returns
| ReferenceKind::Extends
| ReferenceKind::Implements
) && !same_language_family(target_language, reference.language)
{
return None;
}
if reference.reference_kind == ReferenceKind::Imports
&& crosses_known_family(target_language, reference.language)
{
return None;
}
Some(result)
}
#[allow(dead_code)]
pub(super) fn gate_framework_language(
&mut self,
result: Option<ResolvedRef>,
reference: &UnresolvedRef,
) -> Option<ResolvedRef> {
let result = result?;
if !matches!(
reference.reference_kind,
ReferenceKind::References | ReferenceKind::Imports
) {
return Some(result);
}
let target_language = self.get_language_from_node_id(&result.target_node_id);
if target_language != Language::Unknown
&& crosses_known_family(target_language, reference.language)
{
None
} else {
Some(result)
}
}
}