code-moniker-core 0.2.0

Core symbol-graph types and per-language extractors for code-moniker (pure Rust, no pgrx). Consumed by the CLI and the PostgreSQL extension.
Documentation
use tree_sitter::Node;

use crate::core::moniker::{Moniker, MonikerBuilder};

use super::kinds;

pub(super) fn compute_module_moniker(anchor: &Moniker, uri: &str) -> Moniker {
	let stem = strip_rs_extension(uri);
	let mut builder = MonikerBuilder::from_view(anchor.as_view());
	builder.segment(crate::lang::kinds::LANG, b"rs");
	crate::lang::callable::append_dir_module_segments(
		&mut builder,
		stem,
		kinds::DIR,
		kinds::MODULE,
	);
	builder.build()
}

pub(super) fn strip_rs_extension(uri: &str) -> &str {
	uri.strip_suffix(".rs").unwrap_or(uri)
}

pub(super) use crate::lang::callable::CallableSlot;

pub(super) fn function_param_slots(node: Node<'_>, source: &[u8]) -> Vec<CallableSlot> {
	let Some(params) = node.child_by_field_name("parameters") else {
		return Vec::new();
	};
	let mut out = Vec::new();
	let mut cursor = params.walk();
	for child in params.named_children(&mut cursor) {
		match child.kind() {
			"parameter" => {
				let r#type = child
					.child_by_field_name("type")
					.and_then(|n| n.utf8_text(source).ok())
					.map(crate::lang::callable::normalize_type_text)
					.unwrap_or_default();
				let name = child
					.child_by_field_name("pattern")
					.filter(|p| p.kind() == "identifier")
					.and_then(|p| p.utf8_text(source).ok())
					.map(|s| s.as_bytes().to_vec())
					.unwrap_or_default();
				out.push(CallableSlot { name, r#type });
			}
			"variadic_parameter" => out.push(CallableSlot {
				name: Vec::new(),
				r#type: b"...".to_vec(),
			}),
			"self_parameter" => {}
			_ => {}
		}
	}
	out
}

pub(super) fn closure_param_slots(closure: Node<'_>, source: &[u8]) -> Vec<CallableSlot> {
	let Some(params) = closure.child_by_field_name("parameters") else {
		return Vec::new();
	};
	let mut out = Vec::new();
	let mut cursor = params.walk();
	for child in params.named_children(&mut cursor) {
		if child.kind() == "parameter" {
			let r#type = child
				.child_by_field_name("type")
				.and_then(|n| n.utf8_text(source).ok())
				.map(crate::lang::callable::normalize_type_text)
				.unwrap_or_default();
			let name = child
				.child_by_field_name("pattern")
				.filter(|p| p.kind() == "identifier")
				.and_then(|p| p.utf8_text(source).ok())
				.map(|s| s.as_bytes().to_vec())
				.unwrap_or_default();
			out.push(CallableSlot { name, r#type });
		} else if child.kind() == "identifier" {
			let name = child
				.utf8_text(source)
				.map(|s| s.as_bytes().to_vec())
				.unwrap_or_default();
			out.push(CallableSlot {
				name,
				r#type: Vec::new(),
			});
		} else {
			out.push(CallableSlot::default());
		}
	}
	out
}

pub(super) fn impl_type_name<'a>(node: Node<'a>, source: &'a [u8]) -> Option<&'a str> {
	let target = match node.kind() {
		"generic_type" => node.child_by_field_name("type")?,
		_ => node,
	};
	match target.kind() {
		"type_identifier" | "primitive_type" => target.utf8_text(source).ok(),
		"scoped_type_identifier" => target
			.child_by_field_name("name")
			.and_then(|n| n.utf8_text(source).ok()),
		_ => target.utf8_text(source).ok(),
	}
}