code-moniker-core 0.4.0

Core symbol-graph types and per-language extractors for code-moniker.
Documentation
use tree_sitter::Node;

use crate::core::moniker::{Moniker, MonikerBuilder};
use crate::lang::sdk::{RefHints, ResolvedRef};
use crate::lang::tree_util::node_position;

use super::super::kinds;
use super::discover::JavaDiscover;
use super::syntax::{named_children, path_pieces};

#[derive(Clone, Debug)]
pub(super) struct ImportedSymbol {
	pub name: Vec<u8>,
	pub target: Moniker,
	pub confidence: &'static [u8],
	pub is_static: bool,
}

pub(super) fn collect_imports(state: &mut JavaDiscover<'_>, node: Node<'_>, scope: &Moniker) {
	for child in named_children(node) {
		if child.kind() == "import_declaration" {
			import_declaration(state, child, scope);
		} else {
			collect_imports(state, child, scope);
		}
	}
}

fn import_declaration(state: &mut JavaDiscover<'_>, node: Node<'_>, scope: &Moniker) {
	let mut wildcard = false;
	let mut static_import = false;
	let mut path_node = None;
	for index in 0..node.child_count() {
		let Some(child) = node.child(index as u32) else {
			continue;
		};
		match child.kind() {
			"asterisk" => wildcard = true,
			"static" => static_import = true,
			"identifier" | "scoped_identifier" => path_node = Some(child),
			_ => {}
		}
	}
	let Some(path_node) = path_node else {
		return;
	};
	let pieces = path_pieces(path_node, state.source);
	if pieces.is_empty() {
		return;
	}
	let str_pieces = pieces
		.iter()
		.map(|piece| std::str::from_utf8(piece).unwrap_or(""))
		.collect::<Vec<_>>();
	let confidence = external_or_imported(&str_pieces);
	let target = if wildcard {
		wildcard_target(&state.root, &str_pieces, confidence)
	} else {
		symbol_target(&state.root, &str_pieces, confidence)
	};
	let kind = if wildcard {
		kinds::IMPORTS_MODULE
	} else {
		kinds::IMPORTS_SYMBOL
	};
	state.push_ref(ResolvedRef {
		source: scope.clone(),
		target: target.clone(),
		kind,
		position: Some(node_position(node)),
		confidence,
		hints: RefHints::default(),
	});
	if !wildcard && let Some(name) = pieces.last() {
		state.imports.push(ImportedSymbol {
			name: name.clone(),
			target: if static_import {
				static_owner_target(&state.root, &str_pieces, confidence).unwrap_or(target)
			} else {
				target
			},
			confidence,
			is_static: static_import,
		});
	}
}

pub(super) fn wildcard_target(module: &Moniker, pieces: &[&str], confidence: &[u8]) -> Moniker {
	if confidence == kinds::CONF_IMPORTED && !pieces.is_empty() {
		let mut builder = project_regime_builder(module);
		builder.segment(crate::lang::kinds::LANG, b"java");
		for piece in pieces {
			builder.segment(kinds::PACKAGE, piece.as_bytes());
		}
		return builder.build();
	}
	external_package_target(module.as_view().project(), pieces)
}

pub(super) fn symbol_target(module: &Moniker, pieces: &[&str], confidence: &[u8]) -> Moniker {
	if confidence == kinds::CONF_IMPORTED && !pieces.is_empty() {
		let mut builder = project_regime_builder(module);
		builder.segment(crate::lang::kinds::LANG, b"java");
		let last = pieces.len() - 1;
		for (index, piece) in pieces.iter().enumerate() {
			let kind = if index == last {
				kinds::MODULE
			} else {
				kinds::PACKAGE
			};
			builder.segment(kind, piece.as_bytes());
		}
		builder.segment(kinds::PATH, pieces[last].as_bytes());
		return builder.build();
	}
	external_package_target(module.as_view().project(), pieces)
}

fn static_owner_target(module: &Moniker, pieces: &[&str], confidence: &[u8]) -> Option<Moniker> {
	let owner = pieces.get(..pieces.len().checked_sub(1)?)?;
	if owner.is_empty() {
		return None;
	}
	if confidence == kinds::CONF_IMPORTED {
		let mut builder = project_regime_builder(module);
		builder.segment(crate::lang::kinds::LANG, b"java");
		let last = owner.len() - 1;
		for (index, piece) in owner.iter().enumerate() {
			let kind = if index == last {
				kinds::MODULE
			} else {
				kinds::PACKAGE
			};
			builder.segment(kind, piece.as_bytes());
		}
		return Some(builder.build());
	}
	Some(external_package_target(module.as_view().project(), owner))
}

pub(super) fn same_package_symbol_target(module: &Moniker, name: &[u8]) -> Moniker {
	let view = module.as_view();
	let mut builder = MonikerBuilder::new();
	builder.project(view.project());
	for segment in view.segments() {
		if segment.kind == kinds::MODULE {
			break;
		}
		builder.segment(segment.kind, segment.name);
	}
	builder.segment(kinds::MODULE, name);
	builder.segment(kinds::PATH, name);
	builder.build()
}

pub(super) fn java_lang_target(module: &Moniker, name: &[u8]) -> Moniker {
	symbol_target(
		module,
		&["java", "lang", std::str::from_utf8(name).unwrap_or("")],
		kinds::CONF_EXTERNAL,
	)
}

pub(super) fn java_external_target_shape(target: &Moniker) -> bool {
	target
		.as_view()
		.segments()
		.any(|segment| segment.kind == kinds::EXTERNAL_PKG)
}

fn project_regime_builder(module: &Moniker) -> MonikerBuilder {
	let view = module.as_view();
	let mut builder = MonikerBuilder::new();
	builder.project(view.project());
	for segment in view.segments() {
		if segment.kind == crate::lang::kinds::LANG {
			break;
		}
		builder.segment(segment.kind, segment.name);
	}
	builder
}

pub(super) fn external_package_target(project: &[u8], pieces: &[&str]) -> Moniker {
	let mut builder = MonikerBuilder::new();
	builder.project(project);
	if let Some((head, tail)) = pieces.split_first() {
		builder.segment(kinds::EXTERNAL_PKG, head.as_bytes());
		for piece in tail {
			builder.segment(kinds::PATH, piece.as_bytes());
		}
	}
	builder.build()
}

pub(super) fn external_or_imported(pieces: &[&str]) -> &'static [u8] {
	if pieces.is_empty() {
		return kinds::CONF_IMPORTED;
	}
	match pieces[0] {
		"java" | "javax" | "kotlin" | "sun" => kinds::CONF_EXTERNAL,
		"com" if pieces.get(1).copied() == Some("sun") => kinds::CONF_EXTERNAL,
		_ => kinds::CONF_IMPORTED,
	}
}