use tree_sitter::Node;
use super::{extract_direct_call_info, node_text, short_name_of, GrammarHandler, SYNTHETIC_VARARGS_PARAM};
pub(super) fn parameter_container<'tree>(
fn_node: &Node<'tree>,
handler: &GrammarHandler,
) -> Option<Node<'tree>> {
for field in ["parameters", "args", "parameter"] {
if let Some(node) = fn_node.child_by_field_name(field) {
return Some(node);
}
}
let mut pending: Vec<(Node<'tree>, u8)> = Vec::new();
let mut cursor = fn_node.walk();
let mut direct_children: Vec<Node<'tree>> = fn_node.named_children(&mut cursor).collect();
direct_children.reverse();
pending.extend(direct_children.into_iter().map(|child| (child, 0_u8)));
while let Some((node, depth)) = pending.pop() {
if handler.parameter_container_kinds.contains(&node.kind()) {
return Some(node);
}
if depth >= 4
|| handler.fn_kinds.contains(&node.kind())
|| handler.lambda_kinds.contains(&node.kind())
|| handler.parameter_kinds.contains(&node.kind())
|| handler.keyword_parameter_kinds.contains(&node.kind())
{
continue;
}
for index in (0..node.child_count()).rev() {
let Ok(index) = u32::try_from(index) else {
continue;
};
let Some(child) = node.child(index).filter(Node::is_named) else {
continue;
};
if matches!(node.field_name_for_child(index), Some("body" | "block")) {
continue;
}
pending.push((child, depth + 1));
}
}
None
}
pub fn extract_param_annotations(
fn_node: &Node<'_>,
src: &[u8],
handler: &GrammarHandler,
) -> Vec<Vec<String>> {
let mut per_param_annotations: Vec<Vec<String>> = Vec::new();
let visit_param = |param: Node<'_>, into: &mut Vec<Vec<String>>| {
let mut annotation_names: Vec<String> = Vec::new();
let mut annotation_search_roots: Vec<Node<'_>> = Vec::new();
let mut cursor = param.walk();
for child in param.named_children(&mut cursor) {
if handler.parameter_modifier_kinds.contains(&child.kind()) {
annotation_search_roots.push(child);
} else if handler.parameter_annotation_kinds.contains(&child.kind()) {
annotation_search_roots.push(child);
}
}
if annotation_search_roots.is_empty() {
if let Some(type_node) = param.child_by_field_name("type") {
annotation_search_roots.push(type_node);
}
}
if annotation_search_roots.is_empty() {
annotation_search_roots.push(param);
}
for root in annotation_search_roots {
collect_param_annotation_names(root, src, handler, &mut annotation_names);
}
annotation_names.sort();
annotation_names.dedup();
into.push(annotation_names);
};
if let Some(receiver) = fn_node.child_by_field_name("receiver") {
let mut cursor = receiver.walk();
let mut visited_named_child = false;
for param in receiver.named_children(&mut cursor) {
visited_named_child = true;
visit_param(param, &mut per_param_annotations);
}
if !visited_named_child {
visit_param(receiver, &mut per_param_annotations);
}
}
let parameters_container = parameter_container(fn_node, handler);
if let Some(parameters_container) = parameters_container {
let mut cursor = parameters_container.walk();
let mut pending_annotations: Vec<String> = Vec::new();
for param in parameters_container.named_children(&mut cursor) {
if handler.parameter_modifier_kinds.contains(¶m.kind()) {
collect_param_annotation_names(param, src, handler, &mut pending_annotations);
continue;
}
visit_param(param, &mut per_param_annotations);
if let Some(annotations) = per_param_annotations.last_mut() {
annotations.append(&mut pending_annotations);
annotations.sort();
annotations.dedup();
}
}
}
if parameters_container.is_none() {
let mut cursor = fn_node.walk();
let mut previous_identifier_text: Option<String> = None;
for child in fn_node.named_children(&mut cursor) {
if handler.parameter_kinds.contains(&child.kind()) {
if handler.last_identifier_parameter_kinds.contains(&child.kind()) {
let mut selector_annotations = Vec::new();
if let Some(selector_piece) = previous_identifier_text.clone() {
selector_annotations.push(selector_piece);
}
per_param_annotations.push(selector_annotations);
} else {
visit_param(child, &mut per_param_annotations);
}
}
if handler.parameter_selector_kinds.contains(&child.kind()) {
let identifier_text = node_text(&child, src).trim().trim_end_matches(':').to_string();
if !identifier_text.is_empty() {
previous_identifier_text = Some(identifier_text);
}
}
}
}
let mut cursor = fn_node.walk();
for child in fn_node.named_children(&mut cursor) {
if !handler.keyword_parameter_kinds.contains(&child.kind()) {
continue;
}
let mut selector_piece: Option<String> = None;
let mut piece_cursor = child.walk();
for piece_child in child.named_children(&mut piece_cursor) {
if handler.parameter_selector_kinds.contains(&piece_child.kind()) {
let piece_text = node_text(&piece_child, src)
.trim()
.trim_end_matches(':')
.to_string();
if !piece_text.is_empty() {
selector_piece = Some(piece_text);
break;
}
}
}
let mut piece_annotations: Vec<String> = Vec::new();
if let Some(piece) = selector_piece {
piece_annotations.push(piece);
}
per_param_annotations.push(piece_annotations);
}
per_param_annotations
}
fn collect_param_annotation_names(
root: Node<'_>,
src: &[u8],
handler: &GrammarHandler,
collected_names: &mut Vec<String>,
) {
let mut work_stack = vec![root];
while let Some(node) = work_stack.pop() {
if handler.parameter_annotation_kinds.contains(&node.kind()) {
let direct_name = handler
.parameter_annotation_name_extractor
.and_then(|extract| extract(node, src))
.or_else(|| {
node.child_by_field_name("name")
.or_else(|| first_identifier_descendant_for_handler(node, handler))
.map(|name_node| node_text(&name_node, src).trim().to_string())
.filter(|name| !name.is_empty())
});
let constructor_name = direct_name.or_else(|| {
let mut pending = Vec::new();
let mut cursor = node.walk();
let mut children = node.named_children(&mut cursor).collect::<Vec<_>>();
children.reverse();
pending.extend(children);
while let Some(candidate) = pending.pop() {
if handler.is_call(candidate.kind()) {
return extract_direct_call_info(&candidate, src, handler)
.and_then(|(callee, _)| callee)
.map(|callee| short_name_of(&callee).to_string());
}
if handler.parameter_annotation_kinds.contains(&candidate.kind()) {
continue;
}
let mut cursor = candidate.walk();
let mut children = candidate.named_children(&mut cursor).collect::<Vec<_>>();
children.reverse();
pending.extend(children);
}
None
});
if let Some(annotation_name) = constructor_name {
collected_names.push(annotation_name);
}
continue;
}
if handler.is_call(node.kind()) {
if let Some((Some(callee_path), _)) = extract_direct_call_info(&node, src, handler) {
let bare_callee = short_name_of(&callee_path).trim_start_matches('@').to_string();
if !bare_callee.is_empty() {
collected_names.push(bare_callee);
}
}
continue;
}
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
work_stack.push(child);
}
}
}
pub(super) fn extract_param_names(fn_node: &Node<'_>, src: &[u8], handler: &GrammarHandler) -> Vec<String> {
let mut param_names = Vec::new();
if let Some(receiver) = fn_node.child_by_field_name("receiver") {
let mut cursor = receiver.walk();
let mut visited_named_child = false;
for param in receiver.named_children(&mut cursor) {
visited_named_child = true;
push_param_name(param, src, handler, &mut param_names);
}
if !visited_named_child {
push_param_name(receiver, src, handler, &mut param_names);
}
}
let parameters_container = parameter_container(fn_node, handler);
if let Some(parameters_container) = parameters_container {
if handler
.implicit_parameter_kinds
.contains(¶meters_container.kind())
{
let text = node_text(¶meters_container, src).trim();
if !text.is_empty() {
param_names.push(text.to_string());
}
} else if handler.identifier_kinds.contains(¶meters_container.kind()) {
push_param_name(parameters_container, src, handler, &mut param_names);
} else {
let mut cursor = parameters_container.walk();
for param in parameters_container.named_children(&mut cursor) {
if handler.parameter_modifier_kinds.contains(¶m.kind()) {
continue;
}
push_param_name(param, src, handler, &mut param_names);
}
}
let mut cursor = parameters_container.walk();
if cursor.goto_first_child() {
loop {
let child = cursor.node();
if !child.is_named()
&& handler
.anonymous_variadic_token
.is_some_and(|token| node_text(&child, src).trim() == token)
{
if !param_names.iter().any(|name| name == SYNTHETIC_VARARGS_PARAM) {
param_names.push(SYNTHETIC_VARARGS_PARAM.to_string());
}
break;
}
if !cursor.goto_next_sibling() {
break;
}
}
}
}
if parameters_container.is_none() {
let mut cursor = fn_node.walk();
for child in fn_node.named_children(&mut cursor) {
if handler.parameter_kinds.contains(&child.kind()) {
push_param_name(child, src, handler, &mut param_names);
}
}
}
let mut cursor = fn_node.walk();
for child in fn_node.named_children(&mut cursor) {
if !handler.keyword_parameter_kinds.contains(&child.kind()) {
continue;
}
let mut bound_name: Option<String> = None;
let mut piece_cursor = child.walk();
for piece_child in child.named_children(&mut piece_cursor) {
if handler.parameter_kinds.contains(&piece_child.kind()) {
if let Some(name_node) = piece_child
.child_by_field_name("name")
.or_else(|| first_identifier_descendant_for_handler(piece_child, handler))
{
let name_text = node_text(&name_node, src).trim().to_string();
if !name_text.is_empty() {
bound_name = Some(name_text);
break;
}
}
}
}
param_names.push(bound_name.unwrap_or_default());
}
param_names
}
fn push_param_name(param: Node<'_>, src: &[u8], handler: &GrammarHandler, param_names: &mut Vec<String>) {
if handler.self_parameter_kinds.contains(¶m.kind()) {
let name_node = first_identifier_descendant_for_handler(param, handler).unwrap_or(param);
let name = node_text(&name_node, src).trim();
if !name.is_empty() {
param_names.push(name.to_string());
}
return;
}
let repeated_field_names = repeated_named_field_values(param, src, "name", handler);
if repeated_field_names.len() > 1 {
param_names.extend(repeated_field_names);
return;
}
let mut pattern_node = param;
if let Some(left) = pattern_node.child_by_field_name("left") {
pattern_node = left;
}
if handler
.destructured_parameter_kinds
.contains(&pattern_node.kind())
{
let pattern_bindings = binding_names_from_pattern(pattern_node, src, handler);
if !pattern_bindings.is_empty() {
param_names.extend(pattern_bindings);
return;
}
}
let bare_identifier_text = if handler.binding_identifier_kinds.contains(¶m.kind()) {
Some(node_text(¶m, src).to_string())
} else {
None
};
let declarator_chain_name = {
let mut current_declarator = param.child_by_field_name("declarator");
let mut innermost_identifier: Option<Node<'_>> = None;
while let Some(declarator_node) = current_declarator {
if handler.binding_identifier_kinds.contains(&declarator_node.kind()) {
innermost_identifier = Some(declarator_node);
break;
}
if let Some(nested) = declarator_node.child_by_field_name("declarator") {
current_declarator = Some(nested);
} else {
innermost_identifier = first_identifier_descendant_for_handler(declarator_node, handler);
break;
}
}
innermost_identifier
};
let method_param_bound_name = handler
.last_identifier_parameter_kinds
.contains(¶m.kind())
.then(|| last_identifier_descendant_by_position(param, handler))
.flatten();
let name_node = declarator_chain_name
.or(method_param_bound_name)
.or_else(|| param.child_by_field_name("pattern"))
.or_else(|| param.child_by_field_name("name"))
.or_else(|| direct_non_type_identifier_child(param, handler))
.or_else(|| {
if handler.parameter_kinds.contains(¶m.kind())
|| handler.variadic_parameter_kinds.contains(¶m.kind())
{
last_identifier_descendant_by_position(param, handler)
} else {
None
}
})
.or_else(|| first_identifier_descendant_for_handler(param, handler));
if let Some(name_node) = name_node {
if handler.destructured_parameter_kinds.contains(&name_node.kind()) {
let pattern_bindings = binding_names_from_pattern(name_node, src, handler);
if !pattern_bindings.is_empty() {
param_names.extend(pattern_bindings);
return;
}
}
}
let raw_name_text = match (bare_identifier_text, name_node) {
(Some(text), _) => text,
(None, Some(node)) => node_text(&node, src).trim().to_string(),
_ if handler.variadic_parameter_kinds.contains(¶m.kind()) => {
param_names.push(SYNTHETIC_VARARGS_PARAM.to_string());
return;
}
_ => return,
};
let trimmed_name = raw_name_text.trim();
if !trimmed_name.is_empty() && trimmed_name != "*" && trimmed_name != "&" {
param_names.push(trimmed_name.to_string());
}
}
fn binding_names_from_pattern(pattern: Node<'_>, src: &[u8], handler: &GrammarHandler) -> Vec<String> {
let mut bindings = Vec::new();
collect_binding_names(pattern, src, handler, &mut bindings);
bindings
}
fn collect_binding_names(node: Node<'_>, src: &[u8], handler: &GrammarHandler, bindings: &mut Vec<String>) {
if handler.binding_identifier_kinds.contains(&node.kind()) {
let name = node_text(&node, src).trim();
if !name.is_empty() && !bindings.iter().any(|existing| existing == name) {
bindings.push(name.to_string());
}
return;
}
let structural_child = node
.child_by_field_name("left")
.or_else(|| node.child_by_field_name("pattern"))
.or_else(|| node.child_by_field_name("name"))
.or_else(|| node.child_by_field_name("value"));
if let Some(child) = structural_child {
collect_binding_names(child, src, handler, bindings);
return;
}
for index in 0..node.child_count() {
let Ok(index) = u32::try_from(index) else {
continue;
};
let Some(child) = node.child(index).filter(Node::is_named) else {
continue;
};
if matches!(
node.field_name_for_child(index),
Some("type" | "key" | "right" | "value" | "default" | "path" | "constructor")
) {
continue;
}
collect_binding_names(child, src, handler, bindings);
}
}
fn direct_non_type_identifier_child<'tree>(
node: Node<'tree>,
handler: &GrammarHandler,
) -> Option<Node<'tree>> {
let mut cursor = node.walk();
if !cursor.goto_first_child() {
return None;
}
loop {
let child = cursor.node();
if child.is_named()
&& handler.binding_identifier_kinds.contains(&child.kind())
&& cursor.field_name() != Some("type")
{
return Some(child);
}
if !cursor.goto_next_sibling() {
return None;
}
}
}
fn first_identifier_descendant_for_handler<'tree>(
node: Node<'tree>,
handler: &GrammarHandler,
) -> Option<Node<'tree>> {
let mut pending = vec![node];
while let Some(current) = pending.pop() {
if current != node && handler.binding_identifier_kinds.contains(¤t.kind()) {
return Some(current);
}
for index in (0..current.child_count()).rev() {
let Ok(index) = u32::try_from(index) else {
continue;
};
let Some(child) = current.child(index).filter(Node::is_named) else {
continue;
};
if current.field_name_for_child(index) == Some("type") {
continue;
}
pending.push(child);
}
}
None
}
fn repeated_named_field_values(
node: Node<'_>,
src: &[u8],
field: &str,
handler: &GrammarHandler,
) -> Vec<String> {
let mut out = Vec::new();
for idx in 0..node.child_count() {
let Ok(idx) = u32::try_from(idx) else {
continue;
};
let Some(child) = node.child(idx) else {
continue;
};
if node.field_name_for_child(idx) != Some(field) {
continue;
}
if !handler.binding_identifier_kinds.contains(&child.kind()) {
continue;
}
let value = node_text(&child, src).trim();
if !value.is_empty() {
out.push(value.to_string());
}
}
out
}
fn last_identifier_descendant_by_position<'tree>(
node: Node<'tree>,
handler: &GrammarHandler,
) -> Option<Node<'tree>> {
let mut latest_by_position: Option<Node<'tree>> = None;
let mut work_stack = vec![node];
while let Some(current) = work_stack.pop() {
if current != node
&& handler.binding_identifier_kinds.contains(¤t.kind())
&& latest_by_position.is_none_or(|tracked| current.start_byte() > tracked.start_byte())
{
latest_by_position = Some(current);
}
let mut cursor = current.walk();
for child in current.named_children(&mut cursor) {
work_stack.push(child);
}
}
latest_by_position
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kit::{language_from_pack, GENERIC_HANDLER};
fn params_for(pack: &str, function_kind: &str, src: &str) -> Vec<String> {
let language = language_from_pack(pack).unwrap_or_else(|error| panic!("{pack}: {error}"));
let mut parser = tree_sitter::Parser::new();
parser.set_language(&language).expect("set language");
let tree = parser.parse(src.as_bytes(), None).expect("parse source");
let function = find_kind(tree.root_node(), function_kind).expect("function node");
extract_param_names(&function, src.as_bytes(), &GENERIC_HANDLER)
}
fn find_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
let mut pending = vec![root];
while let Some(node) = pending.pop() {
if node.kind() == kind {
return Some(node);
}
let mut cursor = node.walk();
let mut children: Vec<Node<'tree>> = node.named_children(&mut cursor).collect();
children.reverse();
pending.extend(children);
}
None
}
#[test]
fn javascript_destructured_params_follow_pattern_nodes() {
let params = params_for(
"javascript",
"function_declaration",
"function run({ command: cmd, user, nested: { token }, ...rest }, [first, last] = []) {}",
);
assert_eq!(params, ["cmd", "user", "token", "rest", "first", "last"]);
}
#[test]
fn typescript_typed_destructuring_excludes_keys_and_types() {
let params = params_for(
"typescript",
"arrow_function",
"const run = ({ command: cmd, user }: Input, ...rest: string[]) => {};",
);
assert_eq!(params, ["cmd", "user", "rest"]);
}
#[test]
fn c_unnamed_variadic_param_comes_from_grammar_node() {
let params = params_for("c", "function_definition", "void log(const char *fmt, ...) {}");
assert_eq!(params, ["fmt", SYNTHETIC_VARARGS_PARAM]);
}
#[test]
fn cpp_and_lua_variadics_come_from_parameter_nodes() {
assert_eq!(
params_for("cpp", "function_definition", "void log(const char *fmt, ...) {}"),
["fmt", SYNTHETIC_VARARGS_PARAM]
);
assert_eq!(
params_for("lua", "function_declaration", "function log(...) end"),
[SYNTHETIC_VARARGS_PARAM]
);
}
#[test]
fn lambda_parameters_follow_each_grammar_container() {
for (pack, kind, source) in [
("javascript", "arrow_function", "const f = value => sink(value);"),
(
"kotlin",
"lambda_literal",
"val f = { value: String -> sink(value) }",
),
(
"swift",
"lambda_literal",
"let f = { (value: String) in sink(value) }",
),
("elixir", "anonymous_function", "f = fn value -> sink(value) end"),
] {
assert_eq!(params_for(pack, kind, source), ["value"], "{pack}");
}
}
}