use std::collections::HashSet;
use tree_sitter::Node;
use super::receivers::type_fact;
use super::refs::{
base_type_identifier, generic_arg_descriptors, type_descriptor, type_parameter_names,
};
use super::text::{declared_name, is_override, is_public, named_children, text};
use super::types::{ExtensionMethod, Fact};
pub(super) fn raw_method_names(node: Node, src: &[u8], kind: &str) -> Vec<String> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
named_children(body)
.into_iter()
.filter(|c| is_recorded_method(*c, src, kind))
.map(|c| declared_name(c, src))
.filter(|n| !n.is_empty())
.collect()
}
fn is_recorded_method(node: Node, src: &[u8], kind: &str) -> bool {
node.kind() == "method_declaration" && (kind == "interface" || is_public(node, src))
}
pub(super) fn raw_non_public_method_names(node: Node, src: &[u8], kind: &str) -> Vec<String> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
named_children(body)
.into_iter()
.filter(|c| c.kind() == "method_declaration" && !is_recorded_method(*c, src, kind))
.map(|c| declared_name(c, src))
.filter(|n| !n.is_empty())
.collect()
}
pub(super) fn raw_override_method_names(node: Node, src: &[u8]) -> Vec<String> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let mut names: Vec<String> = Vec::new();
for c in named_children(body) {
if c.kind() != "method_declaration" || !is_override(c, src) {
continue;
}
let name = declared_name(c, src);
if !name.is_empty() && !names.contains(&name) {
names.push(name);
}
}
names
}
pub(super) fn raw_method_arities(node: Node, src: &[u8]) -> Vec<(String, Vec<(usize, i64)>)> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let mut pairs: Vec<(String, Vec<(usize, i64)>)> = Vec::new();
for c in named_children(body) {
if c.kind() != "method_declaration" {
continue;
}
let name = declared_name(c, src);
if name.is_empty() {
continue;
}
let range = match c.child_by_field_name("parameters") {
Some(parameters) => method_arity_range(parameters, src),
None => (0, -1),
};
match pairs.iter_mut().find(|(n, _)| *n == name) {
Some((_, ranges)) => ranges.push(range),
None => pairs.push((name, vec![range])),
}
}
pairs
}
pub(super) fn raw_method_params(
node: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Vec<(String, Vec<Vec<String>>)> {
if node.kind() == "delegate_declaration" {
let Some(parameters) = node.child_by_field_name("parameters") else {
return Vec::new();
};
let mut own_type_params = type_params.clone();
own_type_params.extend(type_parameter_names(node, src));
return vec![(
"Invoke".to_string(),
vec![method_param_descriptors(parameters, src, &own_type_params)],
)];
}
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let mut pairs: Vec<(String, Vec<Vec<String>>)> = Vec::new();
for c in named_children(body) {
if c.kind() != "method_declaration" {
continue;
}
let name = declared_name(c, src);
if name.is_empty() {
continue;
}
let mut own_type_params = type_params.clone();
own_type_params.extend(type_parameter_names(c, src));
let descriptors = match c.child_by_field_name("parameters") {
Some(parameters) => method_param_descriptors(parameters, src, &own_type_params),
None => Vec::new(),
};
match pairs.iter_mut().find(|(n, _)| *n == name) {
Some((_, overloads)) => overloads.push(descriptors),
None => pairs.push((name, vec![descriptors])),
}
}
pairs
}
fn method_param_descriptors(
parameters: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Vec<String> {
let mut out = Vec::new();
for i in 0..parameters.child_count() as u32 {
let Some(c) = parameters.child(i) else {
continue;
};
if c.kind() == "parameter" {
let is_this = named_children(c)
.iter()
.any(|m| m.kind() == "modifier" && text(*m, src) == "this");
let descriptor = type_descriptor(c.child_by_field_name("type"), src, type_params);
out.push(if is_this {
format!("this {descriptor}")
} else {
descriptor
});
continue;
}
if parameters.field_name_for_child(i) == Some("type") {
out.push(type_descriptor(Some(c), src, type_params));
}
}
out
}
pub(super) fn raw_property_names(node: Node, src: &[u8]) -> Vec<String> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let mut seen: HashSet<String> = HashSet::new();
let mut names = Vec::new();
for c in named_children(body) {
if c.kind() != "property_declaration" {
continue;
}
let name = declared_name(c, src);
if name.is_empty() || !seen.insert(name.clone()) {
continue;
}
names.push(name);
}
names
}
pub(super) fn raw_property_types(
node: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Vec<(String, Fact)> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let mut seen: HashSet<String> = HashSet::new();
let mut pairs = Vec::new();
for c in named_children(body) {
if c.kind() != "property_declaration" {
continue;
}
let name = declared_name(c, src);
if name.is_empty() || !seen.insert(name.clone()) {
continue;
}
if let Some(fact) = type_fact(c.child_by_field_name("type"), src, type_params) {
pairs.push((name, fact));
}
}
pairs
}
pub(super) fn raw_field_names(node: Node, src: &[u8]) -> Vec<String> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let mut seen: HashSet<String> = HashSet::new();
let mut names = Vec::new();
for c in named_children(body) {
if c.kind() != "field_declaration" {
continue;
}
let Some(vd) = named_children(c)
.into_iter()
.find(|k| k.kind() == "variable_declaration")
else {
continue;
};
for decl in named_children(vd) {
if decl.kind() != "variable_declarator" {
continue;
}
let Some(name) = decl.child_by_field_name("name").map(|n| text(n, src)) else {
continue;
};
if name.is_empty() || !seen.insert(name.clone()) {
continue;
}
names.push(name);
}
}
names
}
pub(super) fn raw_field_types(
node: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Vec<(String, Fact)> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let mut seen: HashSet<String> = HashSet::new();
let mut pairs = Vec::new();
for c in named_children(body) {
if c.kind() != "field_declaration" {
continue;
}
let Some(vd) = named_children(c)
.into_iter()
.find(|k| k.kind() == "variable_declaration")
else {
continue;
};
let Some(fact) = type_fact(vd.child_by_field_name("type"), src, type_params) else {
continue;
};
for decl in named_children(vd) {
if decl.kind() != "variable_declarator" {
continue;
}
let Some(name) = decl.child_by_field_name("name").map(|n| text(n, src)) else {
continue;
};
if name.is_empty() || !seen.insert(name.clone()) {
continue;
}
pairs.push((name, fact.clone()));
}
}
pairs
}
pub(super) fn raw_method_returns(node: Node, src: &[u8], kind: &str) -> Vec<(String, String)> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let mut seen: HashSet<String> = HashSet::new();
let mut pairs = Vec::new();
for c in named_children(body) {
if !is_recorded_method(c, src, kind) {
continue;
}
let name = declared_name(c, src);
if name.is_empty() || !seen.insert(name.clone()) {
continue;
}
if let Some(returns) = base_type_identifier(c.child_by_field_name("returns"), src, false) {
pairs.push((name, returns));
}
}
pairs
}
pub(super) fn raw_method_return_args(
node: Node,
src: &[u8],
kind: &str,
type_params: &HashSet<String>,
) -> Vec<(String, Vec<String>)> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let mut seen: HashSet<String> = HashSet::new();
let mut pairs = Vec::new();
for c in named_children(body) {
if !is_recorded_method(c, src, kind) {
continue;
}
let name = declared_name(c, src);
if name.is_empty() || !seen.insert(name.clone()) {
continue;
}
let returns_node = c.child_by_field_name("returns");
if base_type_identifier(returns_node, src, false).is_none() {
continue;
}
if let Some(args) = generic_arg_descriptors(returns_node, src, type_params) {
pairs.push((name, args));
}
}
pairs
}
fn parameter_arity_range(parameters: Node, src: &[u8], skip_first: bool) -> (usize, i64) {
let mut total: i64 = 0;
let mut min: usize = 0;
let mut unbounded = false;
let mut skip_next = skip_first;
let mut cursor = parameters.walk();
for c in parameters.children(&mut cursor) {
if c.kind() == "params" {
unbounded = true;
continue;
}
if c.kind() != "parameter" {
continue;
}
if skip_next {
skip_next = false;
continue;
}
total += 1;
let is_params = named_children(c)
.iter()
.any(|m| m.kind() == "modifier" && text(*m, src) == "params");
if is_params {
unbounded = true;
} else if !has_default_value(c) {
min += 1;
}
}
(min, if unbounded { -1 } else { total })
}
fn extension_arity_range(parameters: Node, src: &[u8]) -> (usize, i64) {
parameter_arity_range(parameters, src, true)
}
fn method_arity_range(parameters: Node, src: &[u8]) -> (usize, i64) {
parameter_arity_range(parameters, src, false)
}
fn has_default_value(parameter: Node) -> bool {
(0..parameter.child_count() as u32).any(|i| parameter.child(i).map(|c| c.kind()) == Some("="))
}
pub(super) fn raw_extension_methods(node: Node, src: &[u8]) -> Vec<ExtensionMethod> {
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let class_type_params = type_parameter_names(node, src);
let mut seen: HashSet<String> = HashSet::new();
let mut entries = Vec::new();
for c in named_children(body) {
if c.kind() != "method_declaration" {
continue;
}
let Some(parameters) = c.child_by_field_name("parameters") else {
continue;
};
let params = named_children(parameters);
let Some(first) = params.first().copied() else {
continue;
};
if first.kind() != "parameter" {
continue;
}
if !named_children(first)
.iter()
.any(|m| m.kind() == "modifier" && text(*m, src) == "this")
{
continue;
}
let name = declared_name(c, src);
let type_node = first.child_by_field_name("type");
let Some(this_type) = base_type_identifier(type_node, src, true) else {
continue;
};
if name.is_empty() {
continue;
}
let (arity_min, arity_max) = extension_arity_range(parameters, src);
if !seen.insert(format!("{name} {this_type} {arity_min} {arity_max}")) {
continue;
}
let mut type_params = class_type_params.clone();
type_params.extend(type_parameter_names(c, src));
let this_args = generic_arg_descriptors(type_node, src, &type_params);
entries.push(ExtensionMethod {
name,
this_type,
arity_min,
arity_max,
this_args,
});
}
entries
}
pub(super) fn raw_base_names(node: Node, src: &[u8]) -> Vec<String> {
let Some(bl) = named_children(node)
.into_iter()
.find(|c| c.kind() == "base_list")
else {
return Vec::new();
};
let mut names: Vec<String> = Vec::new();
for child in named_children(bl) {
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)
};
if let Some(name) = base_type_identifier(type_node, src, false) {
if !names.contains(&name) {
names.push(name);
}
}
}
names
}
pub(super) fn raw_base_generic_args(
node: Node,
src: &[u8],
type_params: &HashSet<String>,
) -> Vec<(String, Vec<String>)> {
let Some(bl) = 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(bl) {
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;
}
if let Some(args) = generic_arg_descriptors(type_node, src, type_params) {
out.push((name, args));
}
}
out
}
const DIRECT_TEST_ATTRIBUTES: &[&str] = &["Fact", "Theory", "Test", "TestCase", "TestCaseSource"];
const MSTEST_TEST_ATTRIBUTES: &[&str] = &["TestMethod", "DataTestMethod"];
const MSTEST_CLASS_ATTRIBUTE: &str = "TestClass";
const TEST_METHOD_KINDS: &[&str] = &["class", "struct", "record"];
const ATTRIBUTE_SUFFIX: &str = "Attribute";
fn attribute_names(node: Node, src: &[u8]) -> HashSet<String> {
let mut names = HashSet::new();
for list in named_children(node) {
if list.kind() != "attribute_list" {
continue;
}
for attr in named_children(list) {
if attr.kind() != "attribute" {
continue;
}
let Some(name_node) = attr.child_by_field_name("name") else {
continue;
};
let text = text(name_node, src);
if text.is_empty() {
continue;
}
let last = match text.rfind('.') {
Some(i) => &text[i + 1..],
None => &text[..],
};
if last.is_empty() {
continue;
}
names.insert(last.to_string());
if last.len() > ATTRIBUTE_SUFFIX.len() && last.ends_with(ATTRIBUTE_SUFFIX) {
names.insert(last[..last.len() - ATTRIBUTE_SUFFIX.len()].to_string());
}
}
}
names
}
pub(super) fn raw_test_methods(node: Node, src: &[u8], kind: &str) -> Vec<String> {
if !TEST_METHOD_KINDS.contains(&kind) {
return Vec::new();
}
let Some(body) = node.child_by_field_name("body") else {
return Vec::new();
};
let mstest = attribute_names(node, src).contains(MSTEST_CLASS_ATTRIBUTE);
let mut seen: HashSet<String> = HashSet::new();
let mut names = Vec::new();
for c in named_children(body) {
if c.kind() != "method_declaration" {
continue;
}
let attrs = attribute_names(c, src);
let marked = attrs.iter().any(|a| {
DIRECT_TEST_ATTRIBUTES.contains(&a.as_str())
|| (mstest && MSTEST_TEST_ATTRIBUTES.contains(&a.as_str()))
});
if !marked {
continue;
}
let name = declared_name(c, src);
if name.is_empty() || !seen.insert(name.clone()) {
continue;
}
names.push(name);
}
names
}