code-moniker-core 0.4.0

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

use crate::lang::callable::{CallableSlot, normalize_type_text};
use crate::lang::tree_util::node_slice;

pub(super) fn named_children(node: Node<'_>) -> impl Iterator<Item = Node<'_>> {
	let mut cursor = node.walk();
	node.named_children(&mut cursor)
		.collect::<Vec<_>>()
		.into_iter()
}

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();
	};
	flatten_param_slots(params, source)
}

pub(super) fn flatten_param_slots(params: Node<'_>, source: &[u8]) -> Vec<CallableSlot> {
	let mut out = Vec::new();
	for child in named_children(params) {
		match child.kind() {
			"parameter_declaration" => {
				let ty = child
					.child_by_field_name("type")
					.and_then(|n| n.utf8_text(source).ok())
					.map(normalize_type_text)
					.unwrap_or_default();
				let names = parameter_name_bytes(child, source);
				if names.is_empty() {
					out.push(CallableSlot {
						name: Vec::new(),
						r#type: ty,
					});
				} else {
					for name in names {
						out.push(CallableSlot {
							name,
							r#type: ty.clone(),
						});
					}
				}
			}
			"variadic_parameter_declaration" => {
				out.push(CallableSlot {
					name: Vec::new(),
					r#type: b"...".to_vec(),
				});
			}
			_ => {}
		}
	}
	out
}

pub(super) fn parameter_name_bytes(decl: Node<'_>, source: &[u8]) -> Vec<Vec<u8>> {
	let mut out = Vec::new();
	for child in named_children(decl) {
		if child.kind() == "identifier" {
			let name = node_slice(child, source);
			if !name.is_empty() && name != b"_" {
				out.push(name.to_vec());
			}
		}
	}
	out
}

pub(super) fn receiver_type_name<'a>(receiver: Node<'a>, source: &'a [u8]) -> Option<&'a [u8]> {
	let param = named_children(receiver).next()?;
	if param.kind() != "parameter_declaration" {
		return None;
	}
	let type_node = param.child_by_field_name("type")?;
	extract_type_name(type_node, source)
}

pub(super) fn extract_type_name<'a>(node: Node<'a>, source: &'a [u8]) -> Option<&'a [u8]> {
	match node.kind() {
		"type_identifier" => Some(node_slice(node, source)),
		"pointer_type" => {
			let inner = named_children(node).next()?;
			extract_type_name(inner, source)
		}
		"generic_type" => {
			let inner = node.child_by_field_name("type")?;
			extract_type_name(inner, source)
		}
		_ => None,
	}
}

pub(super) fn struct_field_list(struct_node: Node<'_>) -> Option<Node<'_>> {
	named_children(struct_node).find(|child| child.kind() == "field_declaration_list")
}

pub(super) fn strip_string_quotes(raw: &str) -> &str {
	let trimmed = raw
		.strip_prefix('"')
		.and_then(|s| s.strip_suffix('"'))
		.or_else(|| raw.strip_prefix('`').and_then(|s| s.strip_suffix('`')));
	trimmed.unwrap_or(raw)
}

pub(super) fn spec_children<'tree>(node: Node<'tree>, spec_kind: &str) -> Vec<Node<'tree>> {
	let mut out = Vec::new();
	for child in named_children(node) {
		match child.kind() {
			kind if kind == spec_kind => out.push(child),
			"var_spec_list" | "const_spec_list" => {
				for spec in named_children(child) {
					if spec.kind() == spec_kind {
						out.push(spec);
					}
				}
			}
			_ => {}
		}
	}
	out
}

pub(super) fn argument_count(args: Node<'_>) -> usize {
	named_children(args).count()
}

pub(super) fn visibility_from_name(name: &[u8]) -> &'static [u8] {
	match name.first().copied() {
		Some(b) if b.is_ascii_uppercase() => super::kinds::VIS_PUBLIC,
		_ => super::kinds::VIS_MODULE,
	}
}

pub(super) fn receiver_hint_bytes<'src>(operand: Node<'src>, source: &'src [u8]) -> &'src [u8] {
	use crate::lang::kinds::{HINT_CALL, HINT_MEMBER, HINT_SUBSCRIPT};
	match operand.kind() {
		"identifier" => node_slice(operand, source),
		"selector_expression" | "field_identifier" => HINT_MEMBER,
		"call_expression" => HINT_CALL,
		"index_expression" => HINT_SUBSCRIPT,
		_ => b"",
	}
}