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, node_slice};

use super::super::kinds;
use super::builtins::STDLIB_PACKAGES;
use super::discover::GoDiscover;
use super::syntax::{named_children, strip_string_quotes};

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

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

fn import_declaration(state: &mut GoDiscover<'_>, node: Node<'_>, scope: &Moniker) {
	for child in named_children(node) {
		match child.kind() {
			"import_spec" => import_spec(state, child, scope),
			"import_spec_list" => {
				for spec in named_children(child) {
					if spec.kind() == "import_spec" {
						import_spec(state, spec, scope);
					}
				}
			}
			_ => {}
		}
	}
}

fn import_spec(state: &mut GoDiscover<'_>, node: Node<'_>, scope: &Moniker) {
	let Some(path_node) = node.child_by_field_name("path") else {
		return;
	};
	let raw = std::str::from_utf8(node_slice(path_node, state.source)).unwrap_or("");
	let path = strip_string_quotes(raw);
	let pieces: Vec<&str> = path.split('/').filter(|piece| !piece.is_empty()).collect();
	if pieces.is_empty() {
		return;
	}

	let alias_text = node
		.child_by_field_name("name")
		.map(|name| node_slice(name, state.source))
		.and_then(|bytes| std::str::from_utf8(bytes).ok())
		.unwrap_or("");

	let confidence = stdlib_or_imported(&pieces);
	let target = external_package_target(state.root.as_view().project(), &pieces);

	let bind: Option<&[u8]> = match alias_text {
		"" => pieces.last().copied().map(str::as_bytes),
		"." | "_" => None,
		other => Some(other.as_bytes()),
	};
	if let Some(alias) = bind
		&& !alias.is_empty()
	{
		state.imports.push(ImportedPackage {
			alias: alias.to_vec(),
			target: target.clone(),
			confidence,
		});
	}

	state.push_ref(ResolvedRef {
		source: scope.clone(),
		target,
		kind: kinds::IMPORTS_MODULE,
		position: Some(node_position(node)),
		confidence,
		hints: RefHints {
			alias: alias_text.as_bytes().to_vec(),
			..RefHints::default()
		},
	});
}

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()
}

fn stdlib_or_imported(pieces: &[&str]) -> &'static [u8] {
	if pieces.is_empty() {
		return kinds::CONF_IMPORTED;
	}
	if STDLIB_PACKAGES.binary_search(&pieces[0]).is_ok() {
		return kinds::CONF_EXTERNAL;
	}
	kinds::CONF_IMPORTED
}