Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions crates/goose-mcp/src/developer/analyze/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,106 @@ impl Formatter {
output.push('\n');
}

// References (type tracking) - only show if present
if !result.references.is_empty() {
Self::append_references(&mut output, result);
}

output
}

/// Append reference tracking information (method-to-type associations, type usage)
fn append_references(output: &mut String, result: &AnalysisResult) {
use crate::developer::analyze::types::ReferenceType;

// Group references by type
let mut method_defs = Vec::new();
let mut type_inst = Vec::new();
let mut field_types = Vec::new();
let mut var_types = Vec::new();
let mut param_types = Vec::new();

for ref_info in &result.references {
match ref_info.ref_type {
ReferenceType::MethodDefinition => method_defs.push(ref_info),
ReferenceType::TypeInstantiation => type_inst.push(ref_info),
ReferenceType::FieldType => field_types.push(ref_info),
ReferenceType::VariableType => var_types.push(ref_info),
ReferenceType::ParameterType => param_types.push(ref_info),
ReferenceType::Call | ReferenceType::Definition | ReferenceType::Import => {}
}
}

// Only show section if we have non-call references
if method_defs.is_empty()
&& type_inst.is_empty()
&& field_types.is_empty()
&& var_types.is_empty()
&& param_types.is_empty()
{
return;
}

output.push_str("\nR: ");

let mut sections = Vec::new();

// Method definitions (methods associated with types)
if !method_defs.is_empty() {
let mut method_strs: Vec<String> = method_defs
.iter()
.map(|r| {
if let Some(type_name) = &r.associated_type {
format!("{}({})", r.symbol, type_name)
} else {
r.symbol.clone()
}
})
.collect();
method_strs.sort();
method_strs.dedup();
sections.push(format!("methods[{}]", method_strs.join(" ")));
}

// Type instantiations (struct literals)
if !type_inst.is_empty() {
let mut type_names: Vec<String> = type_inst.iter().map(|r| r.symbol.clone()).collect();
type_names.sort();
type_names.dedup();
sections.push(format!("types[{}]", type_names.join(" ")));
}

// Field types (only show unique types, not all occurrences)
if !field_types.is_empty() {
let mut field_type_names: Vec<String> =
field_types.iter().map(|r| r.symbol.clone()).collect();
field_type_names.sort();
field_type_names.dedup();
sections.push(format!("fields[{}]", field_type_names.join(" ")));
}

// Variable types (only show unique types)
if !var_types.is_empty() {
let mut var_type_names: Vec<String> =
var_types.iter().map(|r| r.symbol.clone()).collect();
var_type_names.sort();
var_type_names.dedup();
sections.push(format!("vars[{}]", var_type_names.join(" ")));
}

// Parameter types (only show unique types)
if !param_types.is_empty() {
let mut param_type_names: Vec<String> =
param_types.iter().map(|r| r.symbol.clone()).collect();
param_type_names.sort();
param_type_names.dedup();
sections.push(format!("params[{}]", param_type_names.join(" ")));
}

output.push_str(&sections.join("; "));
output.push('\n');
}

/// Format directory structure with summary
pub fn format_directory_structure(
base_path: &Path,
Expand Down
18 changes: 16 additions & 2 deletions crates/goose-mcp/src/developer/analyze/languages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ type ExtractFunctionNameHandler = fn(&tree_sitter::Node, &str, &str) -> Option<S
/// Takes: (receiver_node, source, ast_recursion_limit)
type FindMethodForReceiverHandler = fn(&tree_sitter::Node, &str, Option<usize>) -> Option<String>;

/// Handler for finding the receiver type from a receiver node
/// Takes: (receiver_node, source)
type FindReceiverTypeHandler = fn(&tree_sitter::Node, &str) -> Option<String>;

/// Language configuration containing all language-specific information
///
/// This struct serves as a single source of truth for language support.
Expand All @@ -61,6 +65,8 @@ pub struct LanguageInfo {
pub extract_function_name_handler: Option<ExtractFunctionNameHandler>,
/// Optional handler for finding method names from receiver nodes
pub find_method_for_receiver_handler: Option<FindMethodForReceiverHandler>,
/// Optional handler for finding receiver type from receiver nodes
pub find_receiver_type_handler: Option<FindReceiverTypeHandler>,
}

/// Get language configuration for a given language
Expand All @@ -76,15 +82,17 @@ pub fn get_language_info(language: &str) -> Option<LanguageInfo> {
function_name_kinds: &["identifier", "field_identifier", "property_identifier"],
extract_function_name_handler: None,
find_method_for_receiver_handler: None,
find_receiver_type_handler: None,
}),
"rust" => Some(LanguageInfo {
element_query: rust::ELEMENT_QUERY,
call_query: rust::CALL_QUERY,
reference_query: "",
reference_query: rust::REFERENCE_QUERY,
function_node_kinds: &["function_item", "impl_item"],
function_name_kinds: &["identifier", "field_identifier", "property_identifier"],
extract_function_name_handler: Some(rust::extract_function_name_for_kind),
find_method_for_receiver_handler: None,
find_method_for_receiver_handler: Some(rust::find_method_for_receiver),
find_receiver_type_handler: Some(rust::find_receiver_type),
}),
"javascript" | "typescript" => Some(LanguageInfo {
element_query: javascript::ELEMENT_QUERY,
Expand All @@ -98,6 +106,7 @@ pub fn get_language_info(language: &str) -> Option<LanguageInfo> {
function_name_kinds: &["identifier", "field_identifier", "property_identifier"],
extract_function_name_handler: None,
find_method_for_receiver_handler: None,
find_receiver_type_handler: None,
}),
"go" => Some(LanguageInfo {
element_query: go::ELEMENT_QUERY,
Expand All @@ -107,6 +116,7 @@ pub fn get_language_info(language: &str) -> Option<LanguageInfo> {
function_name_kinds: &["identifier", "field_identifier", "property_identifier"],
extract_function_name_handler: None,
find_method_for_receiver_handler: Some(go::find_method_for_receiver),
find_receiver_type_handler: None,
}),
"java" => Some(LanguageInfo {
element_query: java::ELEMENT_QUERY,
Expand All @@ -116,6 +126,7 @@ pub fn get_language_info(language: &str) -> Option<LanguageInfo> {
function_name_kinds: &["identifier", "field_identifier", "property_identifier"],
extract_function_name_handler: None,
find_method_for_receiver_handler: None,
find_receiver_type_handler: None,
}),
"kotlin" => Some(LanguageInfo {
element_query: kotlin::ELEMENT_QUERY,
Expand All @@ -125,6 +136,7 @@ pub fn get_language_info(language: &str) -> Option<LanguageInfo> {
function_name_kinds: &["identifier", "field_identifier", "property_identifier"],
extract_function_name_handler: None,
find_method_for_receiver_handler: None,
find_receiver_type_handler: None,
}),
"swift" => Some(LanguageInfo {
element_query: swift::ELEMENT_QUERY,
Expand All @@ -139,6 +151,7 @@ pub fn get_language_info(language: &str) -> Option<LanguageInfo> {
function_name_kinds: &["simple_identifier"],
extract_function_name_handler: Some(swift::extract_function_name_for_kind),
find_method_for_receiver_handler: None,
find_receiver_type_handler: None,
}),
"ruby" => Some(LanguageInfo {
element_query: ruby::ELEMENT_QUERY,
Expand All @@ -148,6 +161,7 @@ pub fn get_language_info(language: &str) -> Option<LanguageInfo> {
function_name_kinds: &["identifier", "field_identifier", "property_identifier"],
extract_function_name_handler: None,
find_method_for_receiver_handler: Some(ruby::find_method_for_receiver),
find_receiver_type_handler: None,
}),
_ => None,
}
Expand Down
94 changes: 94 additions & 0 deletions crates/goose-mcp/src/developer/analyze/languages/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,48 @@ pub const CALL_QUERY: &str = r#"
macro: (identifier) @macro.call)
"#;

/// Tree-sitter query for extracting Rust type references and usage patterns
pub const REFERENCE_QUERY: &str = r#"
; Method receivers - capture self parameters to associate methods with impl types
(self_parameter) @method.receiver

; Struct instantiation - struct literals
(struct_expression
name: (type_identifier) @struct.literal)

; Field type declarations in structs
(field_declaration
type: (type_identifier) @field.type)

; Field with reference type
(field_declaration
type: (reference_type
(type_identifier) @field.type))

; Field with generic type
(field_declaration
type: (generic_type
type: (type_identifier) @field.type))

; Variable type annotations
(let_declaration
type: (type_identifier) @var.type)

; Variable with reference type
(let_declaration
type: (reference_type
(type_identifier) @var.type))

; Function parameter types
(parameter
type: (type_identifier) @param.type)

; Parameter with reference type
(parameter
type: (reference_type
(type_identifier) @param.type))
"#;

/// Extract function name for Rust-specific node kinds
///
/// Rust has special cases like impl_item blocks that should be
Expand All @@ -48,3 +90,55 @@ pub fn extract_function_name_for_kind(
}
None
}

/// Find the method name for a method receiver node in Rust
///
/// The receiver_node is a self_parameter. This walks up to find the
/// containing function_item and returns the method name.
pub fn find_method_for_receiver(
receiver_node: &tree_sitter::Node,
source: &str,
_ast_recursion_limit: Option<usize>,
) -> Option<String> {
// Walk up to find the function_item that contains this self_parameter
let mut current = *receiver_node;

while let Some(parent) = current.parent() {
if parent.kind() == "function_item" {
// Found the function, get its name
for i in 0..parent.child_count() {
if let Some(child) = parent.child(i) {
if child.kind() == "identifier" {
return Some(source[child.byte_range()].to_string());
}
}
}
}
current = parent;
}
None
}

/// Find the receiver type for a self parameter in Rust
///
/// In Rust, self parameters are special - they don't explicitly state their type.
/// This function walks up from a self_parameter node to find the impl block
/// and extracts the type being implemented.
pub fn find_receiver_type(node: &tree_sitter::Node, source: &str) -> Option<String> {
// Walk up from self_parameter to find the impl_item
let mut current = *node;
while let Some(parent) = current.parent() {
if parent.kind() == "impl_item" {
// Find the type_identifier in the impl block
for i in 0..parent.child_count() {
if let Some(child) = parent.child(i) {
if child.kind() == "type_identifier" {
return Some(source[child.byte_range()].to_string());
}
}
}
}
current = parent;
}
None
}
30 changes: 25 additions & 5 deletions crates/goose-mcp/src/developer/analyze/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,19 @@ impl ElementExtractor {
ast_recursion_limit,
);
if let Some(method_name) = method_name {
(
ReferenceType::MethodDefinition,
method_name,
Some(text.to_string()),
)
// Use language-specific handler to find receiver type, or fall back to text
let type_name = Self::find_receiver_type(&node, source, language)
.or_else(|| Some(text.to_string()));

if let Some(type_name) = type_name {
(
ReferenceType::MethodDefinition,
method_name,
Some(type_name),
)
} else {
continue;
}
} else {
continue;
}
Expand Down Expand Up @@ -428,6 +436,18 @@ impl ElementExtractor {
.and_then(|handler| handler(receiver_node, source, ast_recursion_limit))
}

fn find_receiver_type(
receiver_node: &tree_sitter::Node,
source: &str,
language: &str,
) -> Option<String> {
use crate::developer::analyze::languages;

languages::get_language_info(language)
.and_then(|info| info.find_receiver_type_handler)
.and_then(|handler| handler(receiver_node, source))
}

fn find_containing_function(
node: &tree_sitter::Node,
source: &str,
Expand Down
1 change: 1 addition & 0 deletions crates/goose-mcp/src/developer/analyze/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ pub mod integration_tests;
pub mod large_output_tests;
pub mod parser_tests;
pub mod ruby_test;
pub mod rust_test;
pub mod traversal_tests;
Loading
Loading