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
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/goose-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ tree-sitter-go = "0.21"
tree-sitter-java = "0.21"
tree-sitter-kotlin = "0.3.8"
devgen-tree-sitter-swift = "0.21.0"
tree-sitter-ruby = "0.21.0"
streaming-iterator = "0.1"
rayon = "1.10"
libc = "0.2"
Expand Down
42 changes: 42 additions & 0 deletions crates/goose-mcp/src/developer/analyze/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use std::path::PathBuf;

use crate::developer::analyze::types::{AnalysisResult, CallChain};

/// Sentinel value used to represent type references (instantiation, field types, etc.)
/// as callers in the call graph, since they don't have an actual caller function.
const REFERENCE_CALLER: &str = "<reference>";

#[derive(Debug, Clone, Default)]
pub struct CallGraph {
callers: HashMap<String, Vec<(PathBuf, usize, String)>>,
Expand Down Expand Up @@ -60,6 +64,44 @@ impl CallGraph {
));
}
}

for reference in &result.references {
use crate::developer::analyze::types::ReferenceType;

match &reference.ref_type {
ReferenceType::MethodDefinition => {
if let Some(type_name) = &reference.associated_type {
tracing::trace!(
"Linking method {} to type {}",
reference.symbol,
type_name
);
graph.callees.entry(type_name.clone()).or_default().push((
file_path.clone(),
reference.line,
reference.symbol.clone(),
));
}
}
ReferenceType::TypeInstantiation
| ReferenceType::FieldType
| ReferenceType::VariableType
| ReferenceType::ParameterType => {
graph
.callers
.entry(reference.symbol.clone())
.or_default()
.push((
file_path.clone(),
reference.line,
REFERENCE_CALLER.to_string(),
));
}
ReferenceType::Definition | ReferenceType::Call | ReferenceType::Import => {
// These are handled elsewhere or not relevant for type tracking
}
}
}
}

tracing::trace!(
Expand Down
83 changes: 81 additions & 2 deletions crates/goose-mcp/src/developer/analyze/languages/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,96 @@ pub const ELEMENT_QUERY: &str = r#"
(function_declaration name: (identifier) @func)
(method_declaration name: (field_identifier) @func)
(type_declaration (type_spec name: (type_identifier) @struct))
(const_declaration (const_spec name: (identifier) @const))
(import_declaration) @import
"#;

/// Tree-sitter query for extracting Go function calls
/// Tree-sitter query for extracting Go function calls and identifier references
pub const CALL_QUERY: &str = r#"
; Function calls
(call_expression
function: (identifier) @function.call)

; Method calls
(call_expression
function: (selector_expression
field: (field_identifier) @method.call))

; Identifier references in various expression contexts
; This captures constants/variables used in arguments, comparisons, returns, assignments, etc.
(argument_list (identifier) @identifier.reference)
(binary_expression left: (identifier) @identifier.reference)
(binary_expression right: (identifier) @identifier.reference)
(unary_expression operand: (identifier) @identifier.reference)
(return_statement (expression_list (identifier) @identifier.reference))
(assignment_statement right: (expression_list (identifier) @identifier.reference))
"#;

/// Tree-sitter query for extracting Go struct references and usage patterns
pub const REFERENCE_QUERY: &str = r#"
; Method receivers - pointer type
(method_declaration
receiver: (parameter_list
(parameter_declaration
type: (pointer_type (type_identifier) @method.receiver))))

; Method receivers - value type
(method_declaration
receiver: (parameter_list
(parameter_declaration
type: (type_identifier) @method.receiver)))

; Struct literals - simple
(composite_literal
type: (type_identifier) @struct.literal)

; Struct literals - qualified (package.Type)
(composite_literal
type: (qualified_type
name: (type_identifier) @struct.literal))

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

; Field declarations - pointer type
(field_declaration
type: (pointer_type
(type_identifier) @field.type))

; Field declarations - qualified type (package.Type)
(field_declaration
type: (qualified_type
name: (type_identifier) @field.type))

; Field declarations - pointer to qualified type
(field_declaration
type: (pointer_type
(qualified_type
name: (type_identifier) @field.type)))
"#;

/// Find the method name for a method receiver node in Go
///
/// This walks up the tree to find the method_declaration parent and extracts
/// the method name, used for associating methods with their receiver types.
pub fn find_method_for_receiver(
receiver_node: &tree_sitter::Node,
source: &str,
_ast_recursion_limit: Option<usize>,
) -> Option<String> {
let mut current = *receiver_node;
while let Some(parent) = current.parent() {
if parent.kind() == "method_declaration" {
for i in 0..parent.child_count() {
if let Some(child) = parent.child(i) {
if child.kind() == "field_identifier" {
return Some(source[child.byte_range()].to_string());
}
}
}
}
current = parent;
}
None
}
Loading
Loading