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::code_graph::Position;
use crate::core::moniker::Moniker;
use crate::lang::callable::{
	CallableSlot, callable_segment_slots, extend_segment, extend_segment_u32,
};
use crate::lang::sdk::{DiscoveredDef, Namespace};
use crate::lang::tree_util::{find_descendant, node_position, node_slice};

use super::super::kinds;
use super::syntax::path_pieces;

pub(super) struct DefEnv<'a> {
	pub root: &'a Moniker,
	pub source: &'a [u8],
}

pub(super) fn simple_def(
	env: DefEnv<'_>,
	node: Node<'_>,
	scope: &Moniker,
	kind: &'static [u8],
	namespace: Namespace,
	trait_impl: bool,
) -> Option<DiscoveredDef> {
	let name_node = definition_name_node(node)?;
	let name = node_slice(name_node, env.source);
	Some(def_record(DefRecordInput {
		moniker: extend_segment(scope, kind, name),
		parent: scope.clone(),
		namespace,
		name: name.to_vec(),
		kind,
		visibility: def_visibility(kind, node, env.source, scope, trait_impl),
		signature: Vec::new(),
		position: Some(node_position(node)),
		call_name: Vec::new(),
		call_arity: None,
	}))
}

fn definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
	node.child_by_field_name("name").or_else(|| {
		let mut cursor = node.walk();
		node.named_children(&mut cursor)
			.find(|child| child.kind() == "identifier" || child.kind() == "type_identifier")
	})
}

pub(super) fn callable_def(env: DefEnv<'_>, input: CallableDefInput<'_>) -> DiscoveredDef {
	let name = node_slice(input.name_node, env.source);
	let signature = if input.kind == kinds::TEST {
		test_signature(input.node, env.source, name)
	} else {
		Vec::new()
	};
	def_record(DefRecordInput {
		moniker: extend_segment(
			input.scope,
			input.kind,
			&callable_segment_slots(name, input.slots),
		),
		parent: input.scope.clone(),
		namespace: Namespace::Value,
		name: name.to_vec(),
		kind: input.kind,
		visibility: visibility_of(input.node, env.source, input.scope, input.trait_impl),
		position: Some(node_position(input.node)),
		signature,
		call_name: name.to_vec(),
		call_arity: Some(input.slots.len()),
	})
}

pub(super) fn reexport_path_def(
	_env: DefEnv<'_>,
	node: Node<'_>,
	scope: &Moniker,
	name: &[u8],
) -> DiscoveredDef {
	def_record(DefRecordInput {
		moniker: extend_segment(scope, kinds::PATH, name),
		parent: scope.clone(),
		namespace: Namespace::Value,
		name: name.to_vec(),
		kind: kinds::PATH,
		visibility: kinds::VIS_PUBLIC,
		position: Some(node_position(node)),
		signature: Vec::new(),
		call_name: Vec::new(),
		call_arity: None,
	})
}

pub(super) fn proptest_def(
	node: Node<'_>,
	scope: &Moniker,
	name: &[u8],
	param_name: &[u8],
	ignore: Option<&str>,
) -> DiscoveredDef {
	let mut moniker_name = name.to_vec();
	moniker_name.extend_from_slice(b"(");
	moniker_name.extend_from_slice(param_name);
	moniker_name.extend_from_slice(b")");
	def_record(DefRecordInput {
		moniker: extend_segment(scope, kinds::TEST, &moniker_name),
		parent: scope.clone(),
		namespace: Namespace::Value,
		name: name.to_vec(),
		kind: kinds::TEST,
		visibility: kinds::VIS_PRIVATE,
		position: Some(node_position(node)),
		signature: proptest_signature(name, ignore),
		call_name: Vec::new(),
		call_arity: None,
	})
}

pub(super) struct CallableDefInput<'a> {
	pub node: Node<'a>,
	pub name_node: Node<'a>,
	pub scope: &'a Moniker,
	pub kind: &'static [u8],
	pub slots: &'a [CallableSlot],
	pub trait_impl: bool,
}

pub(super) fn enum_constant_def(
	env: DefEnv<'_>,
	node: Node<'_>,
	name_node: Node<'_>,
	enum_moniker: &Moniker,
) -> DiscoveredDef {
	let name = node_slice(name_node, env.source);
	child_value_def(node, enum_moniker, kinds::ENUM_CONSTANT, name.to_vec())
}

pub(super) fn synthetic_enum_constant_def(
	node: Node<'_>,
	enum_moniker: &Moniker,
	name: Vec<u8>,
) -> DiscoveredDef {
	child_value_def(node, enum_moniker, kinds::ENUM_CONSTANT, name)
}

pub(super) fn inferred_struct_def(
	env: DefEnv<'_>,
	node: Node<'_>,
	moniker: &Moniker,
	name: &str,
) -> DiscoveredDef {
	def_record(DefRecordInput {
		moniker: moniker.clone(),
		parent: env.root.clone(),
		namespace: Namespace::Type,
		name: name.as_bytes().to_vec(),
		kind: kinds::STRUCT,
		visibility: kinds::VIS_NONE,
		signature: Vec::new(),
		position: Some(node_position(node)),
		call_name: Vec::new(),
		call_arity: None,
	})
}

pub(super) fn synthetic_lang_enum_def(
	node: Node<'_>,
	scope: &Moniker,
	enum_moniker: &Moniker,
) -> DiscoveredDef {
	def_record(DefRecordInput {
		moniker: enum_moniker.clone(),
		parent: scope.clone(),
		namespace: Namespace::Type,
		name: b"Lang".to_vec(),
		kind: kinds::ENUM,
		visibility: kinds::VIS_NONE,
		signature: Vec::new(),
		position: Some(node_position(node)),
		call_name: Vec::new(),
		call_arity: None,
	})
}

pub(super) fn nested_type_def(
	env: DefEnv<'_>,
	node: Node<'_>,
	function: &Moniker,
	kind: &'static [u8],
	name_node: Node<'_>,
) -> DiscoveredDef {
	let name = node_slice(name_node, env.source);
	def_record(DefRecordInput {
		moniker: extend_segment(function, kind, name),
		parent: function.clone(),
		namespace: Namespace::Type,
		name: name.to_vec(),
		kind,
		visibility: visibility_of(node, env.source, function, false),
		signature: Vec::new(),
		position: Some(node_position(node)),
		call_name: Vec::new(),
		call_arity: None,
	})
}

pub(super) fn local_binding_def(
	function: &Moniker,
	kind: &'static [u8],
	name: Vec<u8>,
	position: Option<Position>,
) -> DiscoveredDef {
	def_record(DefRecordInput {
		moniker: extend_segment(function, kind, &name),
		parent: function.clone(),
		namespace: Namespace::Value,
		name,
		kind,
		visibility: kinds::VIS_NONE,
		signature: Vec::new(),
		position,
		call_name: Vec::new(),
		call_arity: None,
	})
}

pub(super) fn comment_def(scope: &Moniker, start_byte: u32, end_byte: u32) -> DiscoveredDef {
	def_record(DefRecordInput {
		moniker: extend_segment_u32(scope, kinds::COMMENT, start_byte),
		parent: scope.clone(),
		namespace: Namespace::Custom("annotation"),
		name: start_byte.to_string().into_bytes(),
		kind: kinds::COMMENT,
		visibility: kinds::VIS_NONE,
		signature: Vec::new(),
		position: Some((start_byte, end_byte)),
		call_name: Vec::new(),
		call_arity: None,
	})
}

fn child_value_def(
	node: Node<'_>,
	parent: &Moniker,
	kind: &'static [u8],
	name: Vec<u8>,
) -> DiscoveredDef {
	def_record(DefRecordInput {
		moniker: extend_segment(parent, kind, &name),
		parent: parent.clone(),
		namespace: Namespace::Value,
		name,
		kind,
		visibility: kinds::VIS_NONE,
		signature: Vec::new(),
		position: Some(node_position(node)),
		call_name: Vec::new(),
		call_arity: None,
	})
}

fn def_record(input: DefRecordInput) -> DiscoveredDef {
	DiscoveredDef {
		moniker: input.moniker,
		parent: input.parent,
		namespace: input.namespace,
		name: input.name,
		kind: input.kind,
		visibility: input.visibility,
		signature: input.signature,
		position: input.position,
		call_name: input.call_name,
		call_arity: input.call_arity,
	}
}

fn test_signature(node: Node<'_>, source: &[u8], name: &[u8]) -> Vec<u8> {
	let ignore = test_ignore_reason(node, source);
	let mut signature = b"framework=rust-test;enabled=true;display=".to_vec();
	if ignore.is_some() {
		signature = b"framework=rust-test;enabled=false;display=".to_vec();
	}
	signature.extend_from_slice(name);
	if let Some(reason) = ignore.filter(|reason| !reason.is_empty()) {
		signature.extend_from_slice(b";ignore=");
		signature.extend_from_slice(reason.as_bytes());
	}
	signature
}

fn test_ignore_reason(node: Node<'_>, source: &[u8]) -> Option<String> {
	let mut sibling = node.prev_named_sibling();
	while let Some(previous) = sibling {
		if previous.kind() != "attribute_item" {
			break;
		}
		if let Some(reason) = ignore_reason(previous, source) {
			return Some(reason);
		}
		sibling = previous.prev_named_sibling();
	}
	None
}

fn ignore_reason(attribute: Node<'_>, source: &[u8]) -> Option<String> {
	if path_pieces(attribute, source) != vec![b"ignore".to_vec()] {
		return None;
	}
	find_descendant(attribute, "string_content")
		.map(|reason| String::from_utf8_lossy(node_slice(reason, source)).into_owned())
		.or_else(|| Some(String::new()))
}

fn proptest_signature(name: &[u8], ignore: Option<&str>) -> Vec<u8> {
	let mut signature = if ignore.is_some() {
		b"framework=proptest;enabled=false;display=".to_vec()
	} else {
		b"framework=proptest;enabled=true;display=".to_vec()
	};
	signature.extend_from_slice(name);
	if let Some(reason) = ignore.filter(|reason| !reason.is_empty()) {
		signature.extend_from_slice(b";ignore=");
		signature.extend_from_slice(reason.as_bytes());
	}
	signature
}

struct DefRecordInput {
	moniker: Moniker,
	parent: Moniker,
	namespace: Namespace,
	name: Vec<u8>,
	kind: &'static [u8],
	visibility: &'static [u8],
	signature: Vec<u8>,
	position: Option<Position>,
	call_name: Vec<u8>,
	call_arity: Option<usize>,
}

fn visibility_of(
	node: Node<'_>,
	source: &[u8],
	scope: &Moniker,
	trait_impl: bool,
) -> &'static [u8] {
	let mut cursor = node.walk();
	let has_pub = node
		.children(&mut cursor)
		.any(|child| child.kind() == "visibility_modifier" && node_slice(child, source) == b"pub");
	if has_pub || trait_impl || scope.last_kind().as_deref() == Some(kinds::TRAIT) {
		kinds::VIS_PUBLIC
	} else {
		kinds::VIS_PRIVATE
	}
}

fn def_visibility(
	kind: &[u8],
	node: Node<'_>,
	source: &[u8],
	scope: &Moniker,
	trait_impl: bool,
) -> &'static [u8] {
	if kind == kinds::MACRO {
		kinds::VIS_NONE
	} else {
		visibility_of(node, source, scope, trait_impl)
	}
}