code-moniker-workspace 0.4.0

Workspace model, ports, snapshots, linkage, and change analysis for code-moniker.
Documentation
use code_moniker_core::core::moniker::{Moniker, Segment};
use code_moniker_core::lang::{build_manifest::Manifest, kinds};
use rayon::prelude::*;
use rustc_hash::FxHashSet;

use crate::linkage::binding::ReferenceLinkageDecision;
use crate::linkage::catalog::LinkageCandidate;
use crate::linkage::catalog::LinkageQuery;
use crate::linkage::language::LanguageLinkageStrategy;
use crate::snapshot::{RecordTable, ReferenceId, ReferenceRecord};

mod lombok;

pub(super) struct JavaLanguageLinkageStrategy;

impl LanguageLinkageStrategy for JavaLanguageLinkageStrategy {
	fn matches(&self, query: &LinkageQuery<'_>, candidate: &LinkageCandidate<'_>) -> bool {
		candidate.moniker.bind_match(query.target)
			|| query.target.bind_match(candidate.moniker)
			|| java_path_target_matches_type_def(query, candidate)
	}
}

fn java_path_target_matches_type_def(
	query: &LinkageQuery<'_>,
	candidate: &LinkageCandidate<'_>,
) -> bool {
	if query.target_segment_count != candidate.segment_count || query.target_segment_count == 0 {
		return false;
	}
	query
		.target_segments()
		.zip(candidate.moniker.as_view().segments())
		.all(|(target, candidate_segment)| {
			java_segment_matches(query, candidate, target, candidate_segment)
		})
}

fn java_segment_matches(
	query: &LinkageQuery<'_>,
	candidate: &LinkageCandidate<'_>,
	target: Segment<'_>,
	candidate_segment: Segment<'_>,
) -> bool {
	if is_java_source_set_segment(target) && is_java_source_set_segment(candidate_segment) {
		return java_source_set_can_read(target.name, candidate_segment.name);
	}
	if target.kind == candidate_segment.kind {
		return java_segment_name_matches(query, candidate, target, candidate_segment);
	}
	target.kind == kinds::PATH
		&& (is_java_type_kind(candidate_segment.kind)
			|| is_java_static_value_kind(candidate_segment.kind))
		&& target.name == candidate_segment.name
}

fn java_segment_name_matches(
	query: &LinkageQuery<'_>,
	candidate: &LinkageCandidate<'_>,
	target: Segment<'_>,
	candidate_segment: Segment<'_>,
) -> bool {
	if is_java_callable_kind(target.kind) && is_java_callable_kind(candidate_segment.kind) {
		return query
			.call_name
			.is_some_and(|name| Some(name.as_bytes()) == candidate.call_name)
			&& query.call_arity == candidate.call_arity;
	}
	target.name == candidate_segment.name
}

fn is_java_type_kind(kind: &[u8]) -> bool {
	matches!(
		kind,
		kinds::CLASS | kinds::INTERFACE | kinds::RECORD | kinds::ENUM | kinds::ANNOTATION_TYPE
	)
}

fn is_java_callable_kind(kind: &[u8]) -> bool {
	matches!(kind, kinds::METHOD | kinds::CONSTRUCTOR)
}

fn is_java_static_value_kind(kind: &[u8]) -> bool {
	matches!(kind, kinds::ENUM_CONSTANT | kinds::FIELD)
}

fn is_java_source_set_segment(segment: Segment<'_>) -> bool {
	segment.kind == b"srcset"
}

fn java_source_set_can_read(source_set: &[u8], candidate_source_set: &[u8]) -> bool {
	source_set == candidate_source_set || (source_set == b"test" && candidate_source_set == b"main")
}

pub(super) fn package_prefix(target: &Moniker) -> Option<String> {
	let mut pieces = Vec::new();
	let mut in_java = false;
	for segment in target.as_view().segments() {
		if segment.kind == kinds::LANG && segment.name == b"java" {
			in_java = true;
			continue;
		}
		if !in_java {
			continue;
		}
		if segment.kind == kinds::PACKAGE {
			pieces.push(std::str::from_utf8(segment.name).ok()?);
		} else if !pieces.is_empty() {
			break;
		}
	}
	(!pieces.is_empty()).then(|| pieces.join("."))
}

pub(super) fn builtin_external_root(root: &str) -> bool {
	root == "java"
}

pub(super) fn source_declares_external_package(
	manifest: Manifest,
	deps: &FxHashSet<String>,
	package_prefix: &str,
	query_confidence: Option<&str>,
	workspace_declares_package: impl Fn(&str) -> bool,
) -> bool {
	if manifest != Manifest::PomXml {
		return false;
	}
	let has_external_dependency = deps.iter().any(|dep| !workspace_declares_package(dep));
	if query_confidence == Some(confidence(kinds::CONF_IMPORTED)) && has_external_dependency {
		return true;
	}
	deps.iter().any(|dep| {
		!workspace_declares_package(dep)
			&& dependency_group(dep).is_some_and(|group| {
				package_prefix == group
					|| package_prefix
						.strip_prefix(group)
						.is_some_and(|tail| tail.starts_with('.'))
			})
	})
}

fn dependency_group(package: &str) -> Option<&str> {
	let coord = package
		.strip_prefix(Manifest::PomXml.tag())?
		.strip_prefix('\0')?;
	coord.split_once(':').map(|(group, _)| group)
}

pub(super) fn enhance_reference_semantics(
	context: &super::SemanticContext<'_>,
	extends_of: &rustc_hash::FxHashMap<Moniker, Moniker>,
	decisions: &mut [ReferenceLinkageDecision],
	references: &RecordTable<ReferenceRecord>,
	changed_references: Option<&FxHashSet<ReferenceId>>,
) {
	let lombok = lombok::LombokSemantics::build(
		context.material,
		context.candidates,
		references,
		extends_of,
	);
	if lombok.is_empty() {
		return;
	}
	let replacements = decisions
		.par_iter()
		.enumerate()
		.filter_map(|(idx, decision)| {
			if changed_references.is_some_and(|changed| !changed.contains(decision.reference())) {
				return None;
			}
			lombok
				.resolve_reference(decision, references)
				.filter(|replacement| declared_groups_permit(context, replacement))
				.map(|replacement| (idx, replacement))
		})
		.collect::<Vec<_>>();
	for (idx, replacement) in replacements {
		decisions[idx] = replacement;
	}
}

// Semantic rescue must not undo declared connectivity: a Lombok accessor has
// no structural candidate, so a cross-group call arrives here as NoCandidate
// instead of manifest-blocked — this is the only spot where the declared
// verdict can still be enforced for it.
fn declared_groups_permit(
	context: &super::SemanticContext<'_>,
	replacement: &ReferenceLinkageDecision,
) -> bool {
	let ReferenceLinkageDecision::Resolved { targets, .. } = replacement else {
		return true;
	};
	let Some(location) = context.locations.get(replacement.reference_idx()) else {
		return true;
	};
	targets.iter().all(|symbol| {
		context
			.candidates
			.candidate(symbol)
			.is_none_or(|candidate| {
				context.source_groups.link_permission(
					context.material,
					location.source_file,
					candidate.source_file,
				) != Some(crate::linkage::source_groups::LinkPermission::Blocked)
			})
	})
}

fn confidence(value: &[u8]) -> &str {
	std::str::from_utf8(value)
		.unwrap_or_else(|err| panic!("confidence constants must be utf-8: {err}"))
}