use std::collections::HashSet;
use tree_sitter::Node;
use super::qualifiers::{member_name_text, resolve_member_qualifier, Scope};
use super::refs::{
base_type_identifier, invocation_arg_count, type_descriptor, type_parameter_names,
};
use super::text::{named_children, text};
use super::types::{EnclosingCallFact, Fact, PublishRecord};
const PUBLISH_VERBS: &[&str] = &["Publish", "PublishAsync", "SubmitJob", "Reply", "Send"];
pub(super) fn publish_fact(
function: Node,
ns: &str,
type_stack: &[String],
src: &[u8],
scope: &Scope,
) -> Option<PublishRecord> {
let name_node = function.child_by_field_name("name")?;
let verb = member_name_text(Some(name_node), src)?;
let known_verb = PUBLISH_VERBS.contains(&verb.as_str());
let message = match named_message(function, name_node, src, &scope.type_params) {
Some(message) => message,
None if known_verb => identifier_message(function, src, type_stack, scope)?,
None => return None,
};
let enclosing = enclosing_method(function, src);
let pass_through = message == "*"
|| enclosing
.as_ref()
.is_some_and(|(_, params)| params.contains(&message));
let enclosing_method = if pass_through {
if !known_verb {
return None;
}
Some(enclosing?.0)
} else {
None
};
let message = if pass_through {
"*".to_string()
} else {
message
};
Some(PublishRecord {
verb,
message,
namespace: ns.to_string(),
line: function.start_position().row + 1,
outer_types: type_stack.to_vec(),
enclosing_method,
arg_count: invocation_arg_count(function).unwrap_or(0),
enclosing_call: enclosing_call_through_lambda(function, src),
})
}
fn callee_bare_name(function: Node, src: &[u8]) -> Option<String> {
match function.kind() {
"member_access_expression" | "qualified_name" => {
member_name_text(function.child_by_field_name("name"), src)
}
"generic_name" | "identifier" => member_name_text(Some(function), src),
_ => None,
}
}
fn enclosing_call_through_lambda(function: Node, src: &[u8]) -> Option<EnclosingCallFact> {
let mut node = function.parent();
let mut lambda = None;
while let Some(current) = node {
if current.kind() == "lambda_expression" || current.kind() == "anonymous_method_expression"
{
lambda = Some(current);
break;
}
node = current.parent();
}
let lambda = lambda?;
let mut node = lambda.parent();
let argument = loop {
let current = node?;
if current.kind() == "argument" {
break current;
}
node = current.parent();
};
let argument_list = argument.parent().filter(|p| p.kind() == "argument_list")?;
let invocation = argument_list
.parent()
.filter(|p| p.kind() == "invocation_expression")?;
let outer_function = invocation.child_by_field_name("function")?;
let verb = callee_bare_name(outer_function, src)?;
let args = named_children(argument_list);
let arg_position = args.iter().position(|a| a.id() == argument.id())?;
Some(EnclosingCallFact {
verb,
arg_position,
arg_count: args.len(),
})
}
fn named_message(
function: Node,
name_node: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Option<String> {
generic_argument_message(name_node, src, type_params)
.or_else(|| construction_message(function, src, type_params))
}
fn enclosing_method(function: Node, src: &[u8]) -> Option<(String, HashSet<String>)> {
let mut node = function.parent();
while let Some(current) = node {
if current.kind() == "method_declaration" {
let name = member_name_text(current.child_by_field_name("name"), src)?;
return Some((name, type_parameter_names(current, src)));
}
node = current.parent();
}
None
}
fn generic_argument_message(
name_node: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Option<String> {
if name_node.kind() != "generic_name" {
return None;
}
let list = named_children(name_node)
.into_iter()
.find(|c| c.kind() == "type_argument_list")?;
let first = named_children(list).into_iter().next()?;
Some(written_message(Some(first), src, type_params))
}
fn construction_message(
function: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Option<String> {
let call = function.parent()?;
let args = call.child_by_field_name("arguments")?;
let first_arg = named_children(args).into_iter().next()?;
let value = argument_value(first_arg)?;
match find_construction(value)? {
Construction::Typed(ctor) => Some(written_message(
ctor.child_by_field_name("type"),
src,
type_params,
)),
Construction::ImplicitArray(node) => implicit_array_message(node, src, type_params),
}
}
fn identifier_message(
function: Node,
src: &[u8],
type_stack: &[String],
scope: &Scope,
) -> Option<String> {
let call = function.parent()?;
let args = call.child_by_field_name("arguments")?;
let first_arg = named_children(args).into_iter().next()?;
let value = argument_value(first_arg)?;
if value.kind() != "identifier" {
return None;
}
let resolution = resolve_member_qualifier(Some(value), src, type_stack, scope)?;
let fact = resolution.receiver?;
if fact.call.is_some() || fact.lambda.is_some() {
return None;
}
let descriptor = fact_descriptor(&fact);
if fact.is_array {
Some(format!("{descriptor}[]"))
} else {
Some(descriptor)
}
}
fn argument_value(arg: Node) -> Option<Node> {
let name_field = arg.child_by_field_name("name").map(|n| n.id());
named_children(arg)
.into_iter()
.find(|c| Some(c.id()) != name_field)
}
enum Construction<'a> {
Typed(Node<'a>),
ImplicitArray(Node<'a>),
}
fn find_construction(node: Node) -> Option<Construction> {
match node.kind() {
"object_creation_expression" | "array_creation_expression" => {
Some(Construction::Typed(node))
}
"implicit_array_creation_expression" => Some(Construction::ImplicitArray(node)),
_ => {
for child in named_children(node) {
if let Some(found) = find_construction(child) {
return Some(found);
}
}
None
}
}
}
fn implicit_array_message(node: Node, src: &[u8], type_params: &HashSet<String>) -> Option<String> {
let initializer = named_children(node)
.into_iter()
.find(|c| c.kind() == "initializer_expression")?;
let elements = named_children(initializer);
if elements.is_empty() {
return None;
}
let mut element_type: Option<(String, String)> = None;
let mut one_spelling = true;
for element in elements {
if element.kind() != "object_creation_expression" {
return None;
}
let type_node = element.child_by_field_name("type");
let descriptor = type_descriptor(type_node, src, type_params);
let written = written_message(type_node, src, type_params);
match &element_type {
None => element_type = Some((descriptor, written)),
Some((existing, spelled)) if *existing == descriptor => {
one_spelling &= *spelled == written;
}
Some(_) => return None,
}
}
element_type.map(|(bare, written)| {
let element = if one_spelling { written } else { bare };
format!("{element}[]")
})
}
fn written_message(node: Option<Node>, src: &[u8], type_params: &HashSet<String>) -> String {
let rendered = type_descriptor(node, src, type_params);
let mut core = node;
while let Some(n) = core.filter(|n| matches!(n.kind(), "nullable_type" | "array_type")) {
core = n.child_by_field_name("type");
}
let qualifier = core
.filter(|n| n.kind() == "qualified_name")
.and_then(|n| n.child_by_field_name("qualifier"))
.map(|q| {
let mut out = String::new();
push_tokens(q, src, &mut out);
out
});
match qualifier {
Some(qualifier) => format!("{qualifier}.{rendered}"),
None => rendered,
}
}
fn push_tokens(node: Node, src: &[u8], out: &mut String) {
if node.is_extra() {
return;
}
if node.child_count() == 0 {
out.push_str(&text(node, src));
return;
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
push_tokens(child, src, out);
}
}
fn fact_descriptor(fact: &Fact) -> String {
match &fact.args {
Some(args) if !args.is_empty() => format!("{}<{}>", fact.type_name, args.join(",")),
_ => fact.type_name.clone(),
}
}
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,
}
}
pub(super) fn nested_base_type_args(
node: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Vec<(String, Vec<String>)> {
let Some(base_list) = named_children(node)
.into_iter()
.find(|c| c.kind() == "base_list")
else {
return Vec::new();
};
let mut out: Vec<(String, Vec<String>)> = Vec::new();
for child in named_children(base_list) {
if child.kind() == "argument_list" {
continue;
}
let type_node = if child.kind() == "primary_constructor_base_type" {
child.child_by_field_name("type")
} else {
Some(child)
};
let Some(name) = base_type_identifier(type_node, src, false) else {
continue;
};
if out.iter().any(|(k, _)| k == &name) {
continue;
}
let Some(generic) = top_level_generic_name(type_node) else {
continue;
};
let Some(list) = named_children(generic)
.into_iter()
.find(|c| c.kind() == "type_argument_list")
else {
continue;
};
let args = named_children(list);
if args.is_empty() {
continue;
}
let nested = args
.iter()
.any(|a| top_level_generic_name(Some(*a)).is_some());
if !nested {
continue;
}
let descriptors = args
.iter()
.map(|a| type_descriptor(Some(*a), src, type_params))
.collect();
out.push((name, descriptors));
}
out
}
pub(super) fn array_message_bases(
node: Node,
src: &[u8],
_type_params: &HashSet<String>,
) -> Vec<String> {
let Some(base_list) = named_children(node)
.into_iter()
.find(|c| c.kind() == "base_list")
else {
return Vec::new();
};
let mut out: Vec<String> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for child in named_children(base_list) {
if child.kind() == "argument_list" {
continue;
}
let type_node = if child.kind() == "primary_constructor_base_type" {
child.child_by_field_name("type")
} else {
Some(child)
};
let Some(name) = base_type_identifier(type_node, src, false) else {
continue;
};
if !seen.insert(name.clone()) {
continue;
}
let Some(generic) = top_level_generic_name(type_node) else {
continue;
};
let Some(list) = named_children(generic)
.into_iter()
.find(|c| c.kind() == "type_argument_list")
else {
continue;
};
let Some(first) = named_children(list).into_iter().next() else {
continue;
};
if first.kind() == "array_type" {
out.push(name);
}
}
out
}