use std::collections::HashSet;
use tree_sitter::Node;
use super::delegate_args::delegate_argument_param_count;
use super::text::{named_children, text};
use super::types::{Fact, RefRecord, RegistrationRecord};
fn outer_type_name(node: Node, src: &[u8]) -> Option<String> {
match node.kind() {
"nullable_type" | "array_type" => match node.child_by_field_name("type") {
Some(inner) => outer_type_name(inner, src),
None => None,
},
"generic_name" => named_children(node)
.into_iter()
.find(|c| c.kind() == "identifier")
.map(|id| text(id, src)),
"qualified_name" => {
let tail = node.child_by_field_name("name")?;
let full = text(node, src);
let tail_text = text(tail, src);
let normalized_tail = outer_type_name(tail, src)?;
full.strip_suffix(&tail_text)
.map(|prefix| format!("{prefix}{normalized_tail}"))
}
"alias_qualified_name" => match node.child_by_field_name("name") {
Some(n) => outer_type_name(n, src),
None => None,
},
"identifier" => Some(text(node, src)),
_ => None,
}
}
pub(super) fn base_type_identifier(
node: Option<Node>,
src: &[u8],
keep_predefined: bool,
) -> Option<String> {
let node = node?;
let name = match node.kind() {
"nullable_type" | "array_type" => {
base_type_identifier(node.child_by_field_name("type"), src, keep_predefined)
}
"generic_name" => named_children(node)
.into_iter()
.find(|c| c.kind() == "identifier")
.map(|id| text(id, src)),
"qualified_name" | "alias_qualified_name" => {
base_type_identifier(node.child_by_field_name("name"), src, keep_predefined)
}
"identifier" => Some(text(node, src)),
"predefined_type" => {
if keep_predefined {
Some(text(node, src))
} else {
None
}
}
_ => None,
};
name.filter(|n| !n.is_empty())
}
fn top_level_generic_name(node: Option<Node>) -> Option<Node> {
let node = node?;
match node.kind() {
"nullable_type" | "array_type" => top_level_generic_name(node.child_by_field_name("type")),
"qualified_name" | "alias_qualified_name" => {
top_level_generic_name(node.child_by_field_name("name"))
}
"generic_name" => Some(node),
_ => None,
}
}
fn type_argument_arity(node: Option<Node>, src: &[u8]) -> Option<usize> {
let generic = top_level_generic_name(node)?;
let list = named_children(generic)
.into_iter()
.find(|c| c.kind() == "type_argument_list")?;
Some(text(list, src).bytes().filter(|b| *b == b',').count() + 1)
}
pub(super) fn generic_arg_descriptors(
node: Option<Node>,
src: &[u8],
type_params: &HashSet<String>,
) -> Option<Vec<String>> {
let generic = top_level_generic_name(node)?;
let list = named_children(generic)
.into_iter()
.find(|c| c.kind() == "type_argument_list")?;
let args = named_children(list);
if args.is_empty() {
return None;
}
let mut descriptors = Vec::with_capacity(args.len());
for arg in args {
let base = base_type_identifier(Some(arg), src, true)?;
descriptors.push(if type_params.contains(&base) {
"*".to_string()
} else {
base
});
}
Some(descriptors)
}
pub(super) fn type_descriptor(
node: Option<Node>,
src: &[u8],
type_params: &HashSet<String>,
) -> String {
let Some(node) = node else {
return "?".to_string();
};
match node.kind() {
"nullable_type" => type_descriptor(node.child_by_field_name("type"), src, type_params),
"array_type" => {
let element = type_descriptor(node.child_by_field_name("type"), src, type_params);
format!("{element}[]")
}
"qualified_name" | "alias_qualified_name" => {
type_descriptor(node.child_by_field_name("name"), src, type_params)
}
"generic_name" => {
let ident = named_children(node)
.into_iter()
.find(|c| c.kind() == "identifier")
.map(|id| text(id, src))
.unwrap_or_default();
let args = named_children(node)
.into_iter()
.find(|c| c.kind() == "type_argument_list")
.map(|list| {
named_children(list)
.into_iter()
.map(|a| type_descriptor(Some(a), src, type_params))
.collect::<Vec<_>>()
.join(",")
})
.unwrap_or_default();
format!("{ident}<{args}>")
}
"identifier" => {
let name = text(node, src);
if type_params.contains(&name) {
"*".to_string()
} else {
name
}
}
"predefined_type" => text(node, src),
_ => "?".to_string(),
}
}
pub(super) fn type_parameter_names(node: Node, src: &[u8]) -> HashSet<String> {
let mut names = HashSet::new();
let Some(list) = named_children(node)
.into_iter()
.find(|c| c.kind() == "type_parameter_list")
else {
return names;
};
for p in named_children(list) {
if p.kind() != "type_parameter" {
continue;
}
let name = match p.child_by_field_name("name") {
Some(n) => text(n, src),
None => text(p, src),
};
if !name.is_empty() {
names.insert(name);
}
}
names
}
pub(super) fn type_parameter_names_ordered(node: Node, src: &[u8]) -> Vec<String> {
let mut names: Vec<String> = Vec::new();
let Some(list) = named_children(node)
.into_iter()
.find(|c| c.kind() == "type_parameter_list")
else {
return names;
};
for p in named_children(list) {
if p.kind() != "type_parameter" {
continue;
}
let name = match p.child_by_field_name("name") {
Some(n) => text(n, src),
None => text(p, src),
};
if !name.is_empty() && !names.contains(&name) {
names.push(name);
}
}
names
}
pub(super) fn member_qualifier_info(
node: Option<Node>,
src: &[u8],
type_stack: &[String],
) -> Option<(String, bool)> {
let node = node?;
match node.kind() {
"identifier" | "qualified_name" => Some((text(node, src), false)),
"generic_name" => named_children(node)
.into_iter()
.find(|c| c.kind() == "identifier")
.map(|id| (text(id, src), true)),
"this" | "base" => type_stack.last().cloned().map(|name| (name, false)),
"member_access_expression" => {
let inner =
member_qualifier_info(node.child_by_field_name("expression"), src, type_stack);
let name_node = node.child_by_field_name("name")?;
let (inner_text, inner_generic) = inner?;
if name_node.kind() == "generic_name" {
return named_children(name_node)
.into_iter()
.find(|c| c.kind() == "identifier")
.map(|id| (format!("{inner_text}.{}", text(id, src)), true));
}
let name = text(name_node, src);
if name.is_empty() {
return None;
}
Some((format!("{inner_text}.{name}"), inner_generic))
}
_ => None,
}
}
fn invocation_arguments(node: Node) -> Option<Vec<Node>> {
let parent = node.parent()?;
if parent.kind() != "invocation_expression" {
return None;
}
let function = parent.child_by_field_name("function")?;
if function.id() != node.id() {
return None;
}
let args = parent.child_by_field_name("arguments")?;
if args.kind() != "argument_list" {
return None;
}
Some(named_children(args))
}
pub(super) fn invocation_arg_count(node: Node) -> Option<usize> {
Some(invocation_arguments(node)?.len())
}
pub(super) fn invocation_lambda_arg_arity(node: Node, src: &[u8]) -> Option<Vec<Option<usize>>> {
let arities: Vec<Option<usize>> = invocation_arguments(node)?
.into_iter()
.map(|argument| delegate_argument_param_count(argument, src))
.collect();
arities.iter().any(Option::is_some).then_some(arities)
}
pub(super) fn push_ref(
refs: &mut Vec<RefRecord>,
kind: &str,
name: String,
line: usize,
ns: Option<String>,
qualified: Option<String>,
type_stack: &[String],
) {
refs.push(RefRecord {
kind: kind.to_string(),
name,
qualified,
member: None,
line,
namespace: ns,
type_arg_count: None,
generic: false,
receiver_type: None,
arg_count: None,
receiver_args: None,
outer_types: type_stack.to_vec(),
args: None,
receiver_property_owner: None,
receiver_call_owner: None,
receiver_call_member: None,
receiver_base: false,
receiver_awaited: false,
receiver_local: false,
receiver_lambda: None,
receiver_nullable: false,
lambda_arg_arity: None,
});
}
pub(super) fn push_ctor_param_ref(
refs: &mut Vec<RefRecord>,
name: String,
line: usize,
ns: String,
args: Option<Vec<String>>,
type_stack: &[String],
) {
refs.push(RefRecord {
kind: "ctor-param".to_string(),
name,
qualified: None,
member: None,
line,
namespace: Some(ns),
type_arg_count: Some(args.as_ref().map_or(0, Vec::len)),
generic: false,
receiver_type: None,
arg_count: None,
receiver_args: None,
outer_types: type_stack.to_vec(),
args,
receiver_property_owner: None,
receiver_call_owner: None,
receiver_call_member: None,
receiver_base: false,
receiver_awaited: false,
receiver_local: false,
receiver_lambda: None,
receiver_nullable: false,
lambda_arg_arity: None,
});
}
pub(super) fn push_member_ref(
refs: &mut Vec<RefRecord>,
qualifier_text: &str,
member: String,
line: usize,
ns: String,
generic: bool,
receiver: Option<Fact>,
arg_count: Option<usize>,
type_stack: &[String],
property_owner: Option<String>,
receiver_base: bool,
receiver_local: bool,
lambda_arg_arity: Option<Vec<Option<usize>>>,
arity: Option<usize>,
) {
let (
receiver_type,
receiver_args,
receiver_call_owner,
receiver_call_member,
receiver_awaited,
receiver_lambda,
receiver_nullable,
) = match receiver {
Some(Fact {
lambda: Some(slot), ..
}) => (None, None, None, None, false, Some(slot), false),
Some(Fact {
type_name,
call: Some(member),
awaited,
..
}) => (
None,
None,
Some(type_name),
Some(member),
awaited,
None,
false,
),
Some(Fact {
type_name,
args,
call: None,
nullable,
..
}) => (Some(type_name), args, None, None, false, None, nullable),
None => (None, None, None, None, false, None, false),
};
match qualifier_text.rfind('.') {
Some(dot) => refs.push(RefRecord {
kind: "uses-member".to_string(),
name: qualifier_text[dot + 1..].to_string(),
qualified: Some(qualifier_text.to_string()),
member: Some(member),
line,
namespace: Some(ns),
type_arg_count: arity,
generic,
receiver_type,
arg_count,
receiver_args,
outer_types: type_stack.to_vec(),
args: None,
receiver_property_owner: property_owner.clone(),
receiver_call_owner: receiver_call_owner.clone(),
receiver_call_member: receiver_call_member.clone(),
receiver_base,
receiver_awaited,
receiver_local,
receiver_lambda: receiver_lambda.clone(),
receiver_nullable,
lambda_arg_arity: lambda_arg_arity.clone(),
}),
None => refs.push(RefRecord {
kind: "uses-member".to_string(),
name: qualifier_text.to_string(),
qualified: None,
member: Some(member),
line,
namespace: Some(ns),
type_arg_count: arity,
generic,
receiver_type,
arg_count,
receiver_args,
outer_types: type_stack.to_vec(),
args: None,
receiver_property_owner: property_owner,
receiver_call_owner,
receiver_call_member,
receiver_base,
receiver_awaited,
receiver_local,
receiver_lambda,
receiver_nullable,
lambda_arg_arity,
}),
}
}
pub(super) fn record_single_type(
node: Option<Node>,
kind: &str,
ns: &str,
type_stack: &[String],
src: &[u8],
refs: &mut Vec<RefRecord>,
) {
let Some(node) = node else {
return;
};
if node.kind() == "tuple_type" {
for el in named_children(node) {
if el.kind() != "tuple_element" {
continue;
}
record_single_type(
el.child_by_field_name("type"),
kind,
ns,
type_stack,
src,
refs,
);
}
return;
}
let Some(raw) = outer_type_name(node, src) else {
return;
};
let line = node.start_position().row + 1;
let arity = Some(type_argument_arity(Some(node), src).unwrap_or(0));
match raw.rfind('.') {
Some(dot) => push_ref(
refs,
kind,
raw[dot + 1..].to_string(),
line,
Some(ns.to_string()),
Some(raw.clone()),
type_stack,
),
None => push_ref(
refs,
kind,
raw.clone(),
line,
Some(ns.to_string()),
None,
type_stack,
),
}
if let Some(last) = refs.last_mut() {
last.type_arg_count = arity;
}
}
fn is_registration_method_name(name: &str) -> bool {
(name.starts_with("Add") || name.starts_with("TryAdd"))
&& (name.ends_with("Singleton") || name.ends_with("Scoped") || name.ends_with("Transient"))
}
pub(super) fn registration_fact(node: Node, ns: &str, src: &[u8]) -> Option<RegistrationRecord> {
let name_node = node.child_by_field_name("name")?;
if name_node.kind() != "generic_name" {
return None;
}
let ident = named_children(name_node)
.into_iter()
.find(|c| c.kind() == "identifier")?;
if !is_registration_method_name(&text(ident, src)) {
return None;
}
let list = named_children(name_node)
.into_iter()
.find(|c| c.kind() == "type_argument_list")?;
let args = named_children(list);
if args.len() != 2 {
return None;
}
let service = outer_type_name(args[0], src)?;
let implementation = outer_type_name(args[1], src)?;
Some(RegistrationRecord {
service,
implementation,
namespace: ns.to_string(),
line: node.start_position().row + 1,
})
}
pub(super) fn record_base_list(
node: Node,
ns: &str,
type_stack: &[String],
src: &[u8],
refs: &mut Vec<RefRecord>,
) {
let Some(bl) = named_children(node)
.into_iter()
.find(|c| c.kind() == "base_list")
else {
return;
};
for child in named_children(bl) {
if child.kind() == "argument_list" {
continue;
}
if child.kind() == "primary_constructor_base_type" {
record_single_type(
child.child_by_field_name("type"),
"inherits",
ns,
type_stack,
src,
refs,
);
continue;
}
record_single_type(Some(child), "inherits", ns, type_stack, src, refs);
}
}