code-moniker-workspace 0.7.1

Workspace model, ports, snapshots, linkage, and change analysis for code-moniker.
Documentation
use code_moniker_core::lang::{Lang, kinds};

use crate::linkage::catalog::{CandidateCatalog, LinkageQuery, SymbolSet};
use crate::snapshot::ResolutionEvidence;
use crate::source::CodeIndexMaterial;

pub(in crate::linkage) fn prefer_concrete_definitions(
	material: &CodeIndexMaterial,
	catalog: &CandidateCatalog,
	targets: SymbolSet,
) -> SymbolSet {
	if targets.len() < 2 {
		return targets;
	}
	let mut concrete = SymbolSet::new();
	for symbol in targets.iter() {
		let is_alias = catalog.candidate(symbol).is_some_and(|candidate| {
			candidate
				.last_segment
				.is_some_and(|segment| segment.kind == kinds::PATH)
				&& material
					.files
					.get(candidate.source_file)
					.is_some_and(|file| file.lang == Lang::Rs)
		});
		if !is_alias {
			concrete.insert(symbol);
		}
	}
	if concrete.is_empty() || concrete.len() == targets.len() {
		targets
	} else {
		concrete
	}
}

pub(in crate::linkage) fn global_resolution_evidence(
	query: &LinkageQuery<'_>,
) -> ResolutionEvidence {
	if query.reference_kind == "calls"
		&& query
			.material
			.files
			.get(query.source_file)
			.is_some_and(|file| file.lang == Lang::Sql)
		&& !super::sql_call_has_strong_evidence(query)
	{
		ResolutionEvidence::NameMatch
	} else {
		ResolutionEvidence::GlobalBinding
	}
}

pub(in crate::linkage) fn local_resolution_evidence(
	query: &LinkageQuery<'_>,
	candidates: &CandidateCatalog,
	targets: &SymbolSet,
) -> ResolutionEvidence {
	let lang = query
		.material
		.files
		.get(query.source_file)
		.map(|file| file.lang);
	if lang == Some(Lang::Sql) && query.reference_kind == "calls" {
		return if super::sql_call_has_strong_evidence(query) {
			ResolutionEvidence::LocalBinding
		} else {
			ResolutionEvidence::NameMatch
		};
	}
	if lang != Some(Lang::Sql) && query.confidence != Some("name_match") {
		return ResolutionEvidence::LocalBinding;
	}
	if !matches!(lang, Some(Lang::Cs | Lang::Sql)) {
		return ResolutionEvidence::LocalBinding;
	}
	let exact = candidates.indexes().symbol_by_moniker(query.target);
	if exact.is_some_and(|exact| targets.iter().any(|target| target == exact)) {
		ResolutionEvidence::LocalBinding
	} else {
		ResolutionEvidence::NameMatch
	}
}

pub(in crate::linkage) fn confirm_name_match_targets(
	candidates: &CandidateCatalog,
	query: &LinkageQuery<'_>,
	targets: SymbolSet,
) -> SymbolSet {
	if targets.len() <= 1 {
		return targets;
	}
	let targets = match query.confidence {
		Some("name_match") => restrict_to_source_package(candidates, query, targets),
		Some("imported") => targets,
		_ => return targets,
	};
	let source_srcset = file_srcset(query.material, query.source_file);
	prefer_same_srcset(candidates, &source_srcset, targets)
}

fn restrict_to_source_package(
	candidates: &CandidateCatalog,
	query: &LinkageQuery<'_>,
	targets: SymbolSet,
) -> SymbolSet {
	let source_packages = file_package_chain(query.material, query.source_file);
	if source_packages.is_empty() {
		return targets;
	}
	let mut same_package = SymbolSet::new();
	for symbol in targets.iter() {
		let Some(candidate) = candidates.candidate(symbol) else {
			continue;
		};
		if moniker_package_chain(candidate.moniker) == source_packages {
			same_package.insert(symbol);
		}
	}
	same_package
}

fn prefer_same_srcset(
	candidates: &CandidateCatalog,
	source_srcset: &[u8],
	targets: SymbolSet,
) -> SymbolSet {
	if source_srcset.is_empty() || targets.len() <= 1 {
		return targets;
	}
	let mut same_srcset = SymbolSet::new();
	for symbol in targets.iter() {
		let Some(candidate) = candidates.candidate(symbol) else {
			continue;
		};
		if moniker_srcset(candidate.moniker) == source_srcset {
			same_srcset.insert(symbol);
		}
	}
	if same_srcset.is_empty() {
		targets
	} else {
		same_srcset
	}
}

fn file_srcset(material: &CodeIndexMaterial, file_idx: usize) -> Vec<u8> {
	let Some(file) = material.files.get(file_idx) else {
		return Vec::new();
	};
	if file.graph.def_count() == 0 {
		return Vec::new();
	}
	moniker_srcset(&file.graph.def_at(0).moniker)
}

fn moniker_srcset(moniker: &code_moniker_core::core::moniker::Moniker) -> Vec<u8> {
	moniker
		.as_view()
		.segments()
		.find(|segment| segment.kind == b"srcset")
		.map(|segment| segment.name.to_vec())
		.unwrap_or_default()
}

fn file_package_chain(material: &CodeIndexMaterial, file_idx: usize) -> Vec<Vec<u8>> {
	let Some(file) = material.files.get(file_idx) else {
		return Vec::new();
	};
	if file.graph.def_count() == 0 {
		return Vec::new();
	}
	moniker_package_chain(&file.graph.def_at(0).moniker)
}

fn moniker_package_chain(moniker: &code_moniker_core::core::moniker::Moniker) -> Vec<Vec<u8>> {
	moniker
		.as_view()
		.segments()
		.filter(|segment| segment.kind == kinds::PACKAGE)
		.map(|segment| segment.name.to_vec())
		.collect()
}