Skip to main content

code_analyze_mcp/
types.rs

1use crate::analyze::{AnalysisOutput, FileAnalysisOutput, FocusedAnalysisOutput};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fmt::Write;
6
7/// Internal enum wrapping the three analysis output types.
8/// Not serialized; used for type-safe dispatch in lib.rs.
9#[derive(Debug)]
10pub enum ModeResult {
11    Overview(AnalysisOutput),
12    FileDetails(FileAnalysisOutput),
13    SymbolFocus(FocusedAnalysisOutput),
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
17pub struct AnalyzeParams {
18    #[schemars(description = "File or directory path to analyze")]
19    pub path: String,
20
21    #[schemars(
22        description = "Analysis mode. Auto-detected: directory path without focus -> overview; file path -> file_details; focus parameter present -> symbol_focus. Override by setting explicitly."
23    )]
24    #[serde(default)]
25    pub mode: Option<AnalysisMode>,
26
27    #[schemars(
28        description = "Maximum directory traversal depth for overview mode. Unset means unlimited. Use 2-3 for large monorepos to limit output size."
29    )]
30    pub max_depth: Option<u32>,
31
32    #[schemars(
33        description = "Symbol name for symbol_focus mode (required for symbol_focus). Case-sensitive function or method name. Triggers symbol_focus auto-detection when mode is not set explicitly."
34    )]
35    pub focus: Option<String>,
36
37    #[schemars(
38        description = "Call graph traversal depth for symbol_focus mode. Default 1 (callers and callees one level out). Increase for deeper dependency traces; each level multiplies output size."
39    )]
40    pub follow_depth: Option<u32>,
41
42    #[schemars(
43        description = "Maximum AST node recursion depth for tree-sitter queries. Default is sufficient for all standard source files; increase only for pathologically deep nesting in generated code."
44    )]
45    pub ast_recursion_limit: Option<usize>,
46
47    #[schemars(
48        description = "Return full output even when it exceeds the 50K char limit. Prefer summary=true (overview) or narrowing scope over force=true; force=true can produce very large responses."
49    )]
50    pub force: Option<bool>,
51
52    #[schemars(
53        description = "Overview mode primarily; file_details has same 3-way logic (true/false/auto) but size-error still triggers if output exceeds 50K even after summary is applied. true = compact summary (totals plus directory tree, no per-file function lists); false = full output; unset = auto-summarize when output exceeds 50K chars. Use true proactively on large codebases to avoid the size threshold and reduce token consumption."
54    )]
55    pub summary: Option<bool>,
56
57    #[schemars(
58        description = "Pagination cursor from a previous response's next_cursor field. Pass unchanged to retrieve the next page of files (overview) or functions (file_details). Omit on the first call."
59    )]
60    pub cursor: Option<String>,
61
62    #[schemars(
63        description = "Items per page for pagination (default: 100). Items are files in overview mode and functions in file_details mode. Reduce below 100 to limit response size; increase above 100 to reduce round trips."
64    )]
65    pub page_size: Option<usize>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
69pub struct AnalysisResult {
70    pub path: String,
71    pub mode: AnalysisMode,
72    pub import_count: usize,
73    pub main_line: Option<usize>,
74    pub files: Vec<FileInfo>,
75    pub functions: Vec<FunctionInfo>,
76    pub classes: Vec<ClassInfo>,
77    pub references: Vec<ReferenceInfo>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
81pub struct FileInfo {
82    pub path: String,
83    pub language: String,
84    pub line_count: usize,
85    pub function_count: usize,
86    pub class_count: usize,
87    #[schemars(description = "Whether this file is a test file")]
88    pub is_test: bool,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
92pub struct FunctionInfo {
93    pub name: String,
94    pub line: usize,
95    pub end_line: usize,
96    pub parameters: Vec<String>,
97    pub return_type: Option<String>,
98}
99
100impl FunctionInfo {
101    /// Maximum length for parameter display before truncation.
102    const MAX_PARAMS_DISPLAY_LEN: usize = 80;
103    /// Truncation point when parameters exceed MAX_PARAMS_DISPLAY_LEN.
104    const TRUNCATION_POINT: usize = 77;
105
106    /// Format function signature as a single-line string with truncation.
107    /// Returns: `name(param1, param2, ...) -> return_type :start-end`
108    /// Parameters are truncated to ~80 chars with '...' if needed.
109    pub fn compact_signature(&self) -> String {
110        let mut sig = String::with_capacity(self.name.len() + 40);
111        sig.push_str(&self.name);
112        sig.push('(');
113
114        if !self.parameters.is_empty() {
115            let params_str = self.parameters.join(", ");
116            if params_str.len() > Self::MAX_PARAMS_DISPLAY_LEN {
117                // Truncate at a safe char boundary to avoid panicking on multibyte UTF-8.
118                let truncate_at = params_str.floor_char_boundary(Self::TRUNCATION_POINT);
119                sig.push_str(&params_str[..truncate_at]);
120                sig.push_str("...");
121            } else {
122                sig.push_str(&params_str);
123            }
124        }
125
126        sig.push(')');
127
128        if let Some(ret_type) = &self.return_type {
129            sig.push_str(" -> ");
130            sig.push_str(ret_type);
131        }
132
133        write!(sig, " :{}-{}", self.line, self.end_line).ok();
134        sig
135    }
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
139pub struct ClassInfo {
140    pub name: String,
141    pub line: usize,
142    pub end_line: usize,
143    pub methods: Vec<FunctionInfo>,
144    pub fields: Vec<String>,
145    #[schemars(description = "Inherited types (parent classes, interfaces, trait bounds)")]
146    pub inherits: Vec<String>,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
150pub struct CallInfo {
151    pub caller: String,
152    pub callee: String,
153    pub line: usize,
154    pub column: usize,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    #[schemars(description = "Number of arguments passed at the call site")]
157    pub arg_count: Option<usize>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
161pub struct AssignmentInfo {
162    #[schemars(description = "Variable name being assigned")]
163    pub variable: String,
164    #[schemars(description = "Value expression being assigned")]
165    pub value: String,
166    #[schemars(description = "Line number where assignment occurs")]
167    pub line: usize,
168    #[schemars(description = "Enclosing function scope or 'global'")]
169    pub scope: String,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
173pub struct FieldAccessInfo {
174    #[schemars(description = "Object expression being accessed")]
175    pub object: String,
176    #[schemars(description = "Field name being accessed")]
177    pub field: String,
178    #[schemars(description = "Line number where field access occurs")]
179    pub line: usize,
180    #[schemars(description = "Enclosing function scope or 'global'")]
181    pub scope: String,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
185pub struct ReferenceInfo {
186    pub symbol: String,
187    pub reference_type: ReferenceType,
188    pub location: String,
189    pub line: usize,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
193#[serde(rename_all = "lowercase")]
194pub enum ReferenceType {
195    Definition,
196    Usage,
197    Import,
198    Export,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
202#[serde(rename_all = "lowercase")]
203pub enum EntryType {
204    File,
205    Directory,
206    Function,
207    Class,
208    Variable,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
212#[serde(rename_all = "snake_case")]
213pub enum AnalysisMode {
214    Overview,
215    FileDetails,
216    SymbolFocus,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
220pub struct CallChain {
221    pub chain: Vec<CallInfo>,
222    pub depth: u32,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
226pub struct FocusedAnalysisData {
227    pub symbol: String,
228    pub definition: Option<FunctionInfo>,
229    pub call_chains: Vec<CallChain>,
230    pub references: Vec<ReferenceInfo>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
234pub struct ElementQueryResult {
235    pub query: String,
236    pub results: Vec<String>,
237    pub count: usize,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
241pub struct ImportInfo {
242    #[schemars(
243        description = "Full module path excluding the imported symbol (e.g., 'std::collections' for 'use std::collections::HashMap')"
244    )]
245    pub module: String,
246    #[schemars(
247        description = "Imported symbols (e.g., ['HashMap'] for 'use std::collections::HashMap')"
248    )]
249    pub items: Vec<String>,
250    #[schemars(description = "Line number where import appears")]
251    pub line: usize,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
255pub struct SemanticAnalysis {
256    #[schemars(description = "Functions with parameters and return types")]
257    pub functions: Vec<FunctionInfo>,
258    #[schemars(description = "Classes/structs")]
259    pub classes: Vec<ClassInfo>,
260    #[schemars(
261        description = "Flat list of imports; each entry carries its full module path and imported symbols"
262    )]
263    pub imports: Vec<ImportInfo>,
264    #[schemars(description = "Type references with location information")]
265    pub references: Vec<ReferenceInfo>,
266    #[schemars(description = "Call frequency map (function name -> count)")]
267    pub call_frequency: HashMap<String, usize>,
268    #[schemars(description = "Caller-callee pairs extracted from call expressions")]
269    pub calls: Vec<CallInfo>,
270    #[serde(default, skip_serializing_if = "Vec::is_empty")]
271    #[schemars(description = "Variable assignments and reassignments")]
272    pub assignments: Vec<AssignmentInfo>,
273    #[serde(default, skip_serializing_if = "Vec::is_empty")]
274    #[schemars(description = "Field access patterns")]
275    pub field_accesses: Vec<FieldAccessInfo>,
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn test_compact_signature_short_params() {
284        let func = FunctionInfo {
285            name: "add".to_string(),
286            line: 10,
287            end_line: 12,
288            parameters: vec!["a: i32".to_string(), "b: i32".to_string()],
289            return_type: Some("i32".to_string()),
290        };
291
292        let sig = func.compact_signature();
293        assert_eq!(sig, "add(a: i32, b: i32) -> i32 :10-12");
294    }
295
296    #[test]
297    fn test_compact_signature_long_params_truncation() {
298        let func = FunctionInfo {
299            name: "process".to_string(),
300            line: 20,
301            end_line: 50,
302            parameters: vec![
303                "config: ComplexConfigType".to_string(),
304                "data: VeryLongDataStructureNameThatExceedsEightyCharacters".to_string(),
305                "callback: Fn(Result) -> ()".to_string(),
306            ],
307            return_type: Some("Result<Output>".to_string()),
308        };
309
310        let sig = func.compact_signature();
311        assert!(sig.contains("process("));
312        assert!(sig.contains("..."));
313        assert!(sig.contains("-> Result<Output>"));
314        assert!(sig.contains(":20-50"));
315    }
316
317    #[test]
318    fn test_compact_signature_empty_params() {
319        let func = FunctionInfo {
320            name: "main".to_string(),
321            line: 1,
322            end_line: 5,
323            parameters: vec![],
324            return_type: None,
325        };
326
327        let sig = func.compact_signature();
328        assert_eq!(sig, "main() :1-5");
329    }
330}