code-moniker-workspace 0.4.0

Workspace model, ports, snapshots, linkage, and change analysis for code-moniker.
Documentation
// code-moniker: ignore-file[smell-clone-reflex]
// Snapshot views are owned read-model projections from borrowed reference records.
use std::collections::BTreeSet;

use super::model::{
	ReferenceDirection, ReferenceSet, ReferenceSetSummary, ReferenceSummary, SymbolReferences,
};
use super::symbols::SymbolView;
use crate::snapshot::model::{ReferenceId, ReferenceRecord, SymbolId, WorkspaceSnapshot};

pub struct ReferenceView<'a> {
	snapshot: &'a WorkspaceSnapshot,
}

impl<'a> ReferenceView<'a> {
	pub(super) fn new(snapshot: &'a WorkspaceSnapshot) -> Self {
		Self { snapshot }
	}

	pub fn for_symbol(&self, symbol: &SymbolId) -> Option<SymbolReferences> {
		let symbols = SymbolView::new(self.snapshot);
		let record = symbols.find(symbol)?;
		Some(SymbolReferences {
			symbol: symbols.summary(record),
			incoming: self.reference_set(&self.incoming_ids(symbol), ReferenceDirection::Incoming),
			outgoing: self.reference_set(&self.outgoing_ids(symbol), ReferenceDirection::Outgoing),
		})
	}

	pub fn incoming_ids(&self, symbol: &SymbolId) -> Vec<ReferenceId> {
		if let Some(index) = self.snapshot.linkage.read_index.get() {
			return index.incoming(symbol).to_vec();
		}
		self.snapshot
			.linkage
			.resolved
			.iter()
			.filter(|edge| &edge.target == symbol)
			.map(|edge| edge.reference)
			.collect()
	}

	pub fn outgoing_ids(&self, symbol: &SymbolId) -> Vec<ReferenceId> {
		self.snapshot
			.index
			.references
			.file_records(symbol.file())
			.iter()
			.filter(|reference| &reference.source_symbol == symbol)
			.map(|reference| reference.id)
			.collect()
	}

	pub fn reference_set(
		&self,
		refs: &[ReferenceId],
		direction: ReferenceDirection,
	) -> ReferenceSet {
		let groups = refs
			.iter()
			.filter_map(|reference| self.reference(reference))
			.map(|reference| reference_summary(self.snapshot, reference, direction))
			.collect::<Vec<_>>();
		let files = groups
			.iter()
			.filter_map(|group| group.source)
			.collect::<BTreeSet<_>>()
			.len();
		let contexts = groups
			.iter()
			.filter_map(|group| group.context)
			.collect::<BTreeSet<_>>()
			.len();
		ReferenceSet {
			summary: ReferenceSetSummary {
				refs: groups.len(),
				files,
				contexts,
			},
			groups,
		}
	}

	pub fn reference(&self, id: &ReferenceId) -> Option<&'a ReferenceRecord> {
		let record = self
			.snapshot
			.index
			.references
			.file_records(id.file())
			.get(id.reference());
		if let Some(record) = record
			&& &record.id == id
		{
			return Some(record);
		}
		self.snapshot
			.index
			.references
			.iter()
			.find(|reference| &reference.id == id)
	}
}

fn reference_summary(
	snapshot: &WorkspaceSnapshot,
	reference: &ReferenceRecord,
	direction: ReferenceDirection,
) -> ReferenceSummary {
	let symbols = SymbolView::new(snapshot);
	let source_symbol = symbols.find(&reference.source_symbol);
	let resolved_target = resolved_target(snapshot, &reference.id);
	let external_target = external_target(snapshot, &reference.id);
	ReferenceSummary {
		reference: reference.id,
		source: Some(reference.source),
		context: source_symbol.map(|symbol| symbol.id),
		actor: source_symbol
			.map(|symbol| symbol.name.clone())
			.unwrap_or_else(|| reference.source_symbol.to_string()),
		endpoint_label: match direction {
			ReferenceDirection::Incoming => "source",
			ReferenceDirection::Outgoing => "target",
		},
		endpoint: resolved_target
			.as_ref()
			.and_then(|symbol| symbols.find(symbol))
			.map(|symbol| symbol.identity.to_string())
			.or(external_target)
			.unwrap_or_else(|| reference.target_identity.to_string()),
		kind: reference.kind.clone(),
		line_range: reference.line_range,
	}
}

fn resolved_target(snapshot: &WorkspaceSnapshot, reference: &ReferenceId) -> Option<SymbolId> {
	if let Some(index) = snapshot.linkage.read_index.get() {
		return index.resolved_target(reference).cloned();
	}
	snapshot
		.linkage
		.resolved
		.iter()
		.find(|edge| &edge.reference == reference)
		.map(|edge| edge.target)
}

fn external_target(snapshot: &WorkspaceSnapshot, reference: &ReferenceId) -> Option<String> {
	snapshot
		.linkage
		.external
		.iter()
		.find(|external| &external.reference == reference)
		.map(|external| external.target_identity.to_string())
}