1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fmt::Write;
5
6#[allow(unused_imports)]
7use crate::analyze::{AnalysisOutput, FileAnalysisOutput, FocusedAnalysisOutput};
8
9#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
11pub struct PaginationParams {
12 pub cursor: Option<String>,
14 pub page_size: Option<usize>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
20pub struct OutputControlParams {
21 pub force: Option<bool>,
23 pub summary: Option<bool>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
28pub struct AnalyzeDirectoryParams {
29 pub path: String,
31
32 pub max_depth: Option<u32>,
34
35 #[serde(flatten)]
36 pub pagination: PaginationParams,
37
38 #[serde(flatten)]
39 pub output_control: OutputControlParams,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
43pub struct AnalyzeFileParams {
44 pub path: String,
46
47 pub ast_recursion_limit: Option<usize>,
49
50 #[serde(flatten)]
51 pub pagination: PaginationParams,
52
53 #[serde(flatten)]
54 pub output_control: OutputControlParams,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
58pub struct AnalyzeSymbolParams {
59 pub path: String,
61
62 pub symbol: String,
64
65 pub follow_depth: Option<u32>,
67
68 pub max_depth: Option<u32>,
70
71 pub ast_recursion_limit: Option<usize>,
73
74 #[serde(flatten)]
75 pub pagination: PaginationParams,
76
77 #[serde(flatten)]
78 pub output_control: OutputControlParams,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
82pub struct AnalysisResult {
83 pub path: String,
84 pub mode: AnalysisMode,
85 pub import_count: usize,
86 pub main_line: Option<usize>,
87 pub files: Vec<FileInfo>,
88 pub functions: Vec<FunctionInfo>,
89 pub classes: Vec<ClassInfo>,
90 pub references: Vec<ReferenceInfo>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
94pub struct FileInfo {
95 pub path: String,
96 pub language: String,
97 pub line_count: usize,
98 pub function_count: usize,
99 pub class_count: usize,
100 pub is_test: bool,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
105pub struct FunctionInfo {
106 pub name: String,
107 pub line: usize,
108 pub end_line: usize,
109 pub parameters: Vec<String>,
111 pub return_type: Option<String>,
112}
113
114impl FunctionInfo {
115 const MAX_PARAMS_DISPLAY_LEN: usize = 80;
117 const TRUNCATION_POINT: usize = 77;
119
120 pub fn compact_signature(&self) -> String {
124 let mut sig = String::with_capacity(self.name.len() + 40);
125 sig.push_str(&self.name);
126 sig.push('(');
127
128 if !self.parameters.is_empty() {
129 let params_str = self.parameters.join(", ");
130 if params_str.len() > Self::MAX_PARAMS_DISPLAY_LEN {
131 let truncate_at = params_str.floor_char_boundary(Self::TRUNCATION_POINT);
133 sig.push_str(¶ms_str[..truncate_at]);
134 sig.push_str("...");
135 } else {
136 sig.push_str(¶ms_str);
137 }
138 }
139
140 sig.push(')');
141
142 if let Some(ret_type) = &self.return_type {
143 sig.push_str(" -> ");
144 sig.push_str(ret_type);
145 }
146
147 write!(sig, " :{}-{}", self.line, self.end_line).ok();
148 sig
149 }
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
153pub struct ClassInfo {
154 pub name: String,
155 pub line: usize,
156 pub end_line: usize,
157 pub methods: Vec<FunctionInfo>,
158 pub fields: Vec<String>,
159 #[schemars(description = "Inherited types (parent classes, interfaces, trait bounds)")]
161 pub inherits: Vec<String>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
165pub struct CallInfo {
166 pub caller: String,
167 pub callee: String,
168 pub line: usize,
169 pub column: usize,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub arg_count: Option<usize>,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
176pub struct AssignmentInfo {
177 pub variable: String,
179 pub value: String,
181 pub line: usize,
183 pub scope: String,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
188pub struct FieldAccessInfo {
189 pub object: String,
191 pub field: String,
193 pub line: usize,
195 pub scope: String,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
200pub struct ReferenceInfo {
201 pub symbol: String,
202 pub reference_type: ReferenceType,
203 pub location: String,
204 pub line: usize,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
208#[serde(rename_all = "lowercase")]
209pub enum ReferenceType {
210 Definition,
211 Usage,
212 Import,
213 Export,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
217#[serde(rename_all = "lowercase")]
218pub enum EntryType {
219 File,
220 Directory,
221 Function,
222 Class,
223 Variable,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
228#[serde(rename_all = "snake_case")]
229pub enum AnalysisMode {
230 Overview,
232 FileDetails,
234 SymbolFocus,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
239pub struct CallChain {
240 pub chain: Vec<CallInfo>,
241 pub depth: u32,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
245pub struct FocusedAnalysisData {
246 pub symbol: String,
247 pub definition: Option<FunctionInfo>,
248 pub call_chains: Vec<CallChain>,
249 pub references: Vec<ReferenceInfo>,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
253pub struct ElementQueryResult {
254 pub query: String,
255 pub results: Vec<String>,
256 pub count: usize,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
260pub struct ImportInfo {
261 pub module: String,
263 pub items: Vec<String>,
265 pub line: usize,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
270pub struct SemanticAnalysis {
271 pub functions: Vec<FunctionInfo>,
272 pub classes: Vec<ClassInfo>,
273 pub imports: Vec<ImportInfo>,
275 pub references: Vec<ReferenceInfo>,
276 pub call_frequency: HashMap<String, usize>,
278 pub calls: Vec<CallInfo>,
280 #[serde(default, skip_serializing_if = "Vec::is_empty")]
282 pub assignments: Vec<AssignmentInfo>,
283 #[serde(default, skip_serializing_if = "Vec::is_empty")]
285 pub field_accesses: Vec<FieldAccessInfo>,
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn test_compact_signature_short_params() {
294 let func = FunctionInfo {
295 name: "add".to_string(),
296 line: 10,
297 end_line: 12,
298 parameters: vec!["a: i32".to_string(), "b: i32".to_string()],
299 return_type: Some("i32".to_string()),
300 };
301
302 let sig = func.compact_signature();
303 assert_eq!(sig, "add(a: i32, b: i32) -> i32 :10-12");
304 }
305
306 #[test]
307 fn test_compact_signature_long_params_truncation() {
308 let func = FunctionInfo {
309 name: "process".to_string(),
310 line: 20,
311 end_line: 50,
312 parameters: vec![
313 "config: ComplexConfigType".to_string(),
314 "data: VeryLongDataStructureNameThatExceedsEightyCharacters".to_string(),
315 "callback: Fn(Result) -> ()".to_string(),
316 ],
317 return_type: Some("Result<Output>".to_string()),
318 };
319
320 let sig = func.compact_signature();
321 assert!(sig.contains("process("));
322 assert!(sig.contains("..."));
323 assert!(sig.contains("-> Result<Output>"));
324 assert!(sig.contains(":20-50"));
325 }
326
327 #[test]
328 fn test_compact_signature_empty_params() {
329 let func = FunctionInfo {
330 name: "main".to_string(),
331 line: 1,
332 end_line: 5,
333 parameters: vec![],
334 return_type: None,
335 };
336
337 let sig = func.compact_signature();
338 assert_eq!(sig, "main() :1-5");
339 }
340
341 #[test]
342 fn schema_flatten_inline() {
343 use schemars::schema_for;
344
345 let dir_schema = schema_for!(AnalyzeDirectoryParams);
347 let dir_props = dir_schema
348 .as_object()
349 .and_then(|o| o.get("properties"))
350 .and_then(|v| v.as_object())
351 .expect("AnalyzeDirectoryParams must have properties");
352
353 assert!(
354 dir_props.contains_key("cursor"),
355 "cursor must be top-level in AnalyzeDirectoryParams schema"
356 );
357 assert!(
358 dir_props.contains_key("page_size"),
359 "page_size must be top-level in AnalyzeDirectoryParams schema"
360 );
361 assert!(
362 dir_props.contains_key("force"),
363 "force must be top-level in AnalyzeDirectoryParams schema"
364 );
365 assert!(
366 dir_props.contains_key("summary"),
367 "summary must be top-level in AnalyzeDirectoryParams schema"
368 );
369
370 let file_schema = schema_for!(AnalyzeFileParams);
372 let file_props = file_schema
373 .as_object()
374 .and_then(|o| o.get("properties"))
375 .and_then(|v| v.as_object())
376 .expect("AnalyzeFileParams must have properties");
377
378 assert!(
379 file_props.contains_key("cursor"),
380 "cursor must be top-level in AnalyzeFileParams schema"
381 );
382 assert!(
383 file_props.contains_key("page_size"),
384 "page_size must be top-level in AnalyzeFileParams schema"
385 );
386 assert!(
387 file_props.contains_key("force"),
388 "force must be top-level in AnalyzeFileParams schema"
389 );
390 assert!(
391 file_props.contains_key("summary"),
392 "summary must be top-level in AnalyzeFileParams schema"
393 );
394
395 let symbol_schema = schema_for!(AnalyzeSymbolParams);
397 let symbol_props = symbol_schema
398 .as_object()
399 .and_then(|o| o.get("properties"))
400 .and_then(|v| v.as_object())
401 .expect("AnalyzeSymbolParams must have properties");
402
403 assert!(
404 symbol_props.contains_key("cursor"),
405 "cursor must be top-level in AnalyzeSymbolParams schema"
406 );
407 assert!(
408 symbol_props.contains_key("page_size"),
409 "page_size must be top-level in AnalyzeSymbolParams schema"
410 );
411 assert!(
412 symbol_props.contains_key("force"),
413 "force must be top-level in AnalyzeSymbolParams schema"
414 );
415 assert!(
416 symbol_props.contains_key("summary"),
417 "summary must be top-level in AnalyzeSymbolParams schema"
418 );
419 }
420}