1use serde::Serialize;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
7pub enum Confidence {
8 High,
9 Low,
10}
11
12impl std::fmt::Display for Confidence {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 match self {
15 Confidence::High => write!(f, "HIGH"),
16 Confidence::Low => write!(f, "LOW"),
17 }
18 }
19}
20
21#[derive(Clone, Debug, Serialize)]
23pub struct StringLiteralMatch {
24 pub file: String,
25 pub line: usize,
26 pub context: String, }
28
29#[derive(Clone, Serialize)]
30pub struct CommandGap {
31 pub name: String,
32 pub implementation_name: Option<String>,
33 pub locations: Vec<(String, usize)>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub confidence: Option<Confidence>,
37 #[serde(skip_serializing_if = "Vec::is_empty", default)]
39 pub string_literal_matches: Vec<StringLiteralMatch>,
40}
41
42#[derive(Clone, Serialize)]
43pub struct AiInsight {
44 pub title: String,
45 pub severity: String,
46 pub message: String,
47}
48
49#[derive(Clone, Serialize)]
50pub struct GraphNode {
51 pub id: String,
52 pub label: String,
53 pub loc: usize,
54 pub x: f32,
55 pub y: f32,
56 pub component: usize,
57 pub degree: usize,
58 pub detached: bool,
59}
60
61#[derive(Clone, Serialize)]
62pub struct GraphComponent {
63 pub id: usize,
64 pub size: usize,
65 #[serde(rename = "edges")]
66 pub edge_count: usize,
67 pub nodes: Vec<String>,
68 pub isolated_count: usize,
69 pub sample: String,
70 pub loc_sum: usize,
71 pub detached: bool,
72 pub tauri_frontend: usize,
73 pub tauri_backend: usize,
74}
75
76#[derive(Clone, Serialize)]
77pub struct GraphData {
78 pub nodes: Vec<GraphNode>,
79 pub edges: Vec<(String, String, String)>, pub components: Vec<GraphComponent>,
81 pub main_component_id: usize,
82 #[serde(default)]
84 pub truncated: bool,
85 #[serde(default)]
87 pub total_nodes: usize,
88 #[serde(default)]
90 pub total_edges: usize,
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub truncation_reason: Option<String>,
94}
95
96#[derive(Clone, Serialize)]
98pub struct DupLocation {
99 pub file: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub line: Option<usize>,
102}
103
104#[derive(Clone, Serialize)]
105pub struct RankedDup {
106 pub name: String,
107 pub files: Vec<String>,
108 #[serde(skip_serializing_if = "Vec::is_empty", default)]
110 pub locations: Vec<DupLocation>,
111 pub score: usize,
112 pub prod_count: usize,
113 pub dev_count: usize,
114 pub canonical: String,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub canonical_line: Option<usize>,
118 pub refactors: Vec<String>,
119}
120
121#[derive(Clone, Serialize)]
124pub struct CommandBridge {
125 pub name: String,
127 pub fe_locations: Vec<(String, usize)>,
129 pub be_location: Option<(String, usize, String)>,
131 pub status: String,
133 pub language: String,
135}
136
137#[derive(Clone, Default, Serialize)]
138pub struct TreeNode {
139 pub path: String,
140 pub loc: usize,
141 #[serde(default)]
142 pub children: Vec<TreeNode>,
143}
144
145#[derive(Serialize)]
146pub struct ReportSection {
147 pub root: String,
148 pub files_analyzed: usize,
149 pub total_loc: usize,
150 pub reexport_files_count: usize,
151 pub dynamic_imports_count: usize,
152 pub ranked_dups: Vec<RankedDup>,
153 pub cascades: Vec<(String, String)>,
154 #[serde(default, skip_serializing_if = "Vec::is_empty")]
156 pub circular_imports: Vec<Vec<String>>,
157 pub dynamic: Vec<(String, Vec<String>)>,
158 pub analyze_limit: usize,
159 pub missing_handlers: Vec<CommandGap>,
160 pub unregistered_handlers: Vec<CommandGap>,
163 pub unused_handlers: Vec<CommandGap>,
164 pub command_counts: (usize, usize),
165 pub command_bridges: Vec<CommandBridge>,
167 pub open_base: Option<String>,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub tree: Option<Vec<TreeNode>>,
170 pub graph: Option<GraphData>,
171 pub graph_warning: Option<String>,
172 pub insights: Vec<AiInsight>,
173 pub git_branch: Option<String>,
174 pub git_commit: Option<String>,
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::snapshot::CommandBridge;
181
182 #[test]
183 fn confidence_display_high() {
184 assert_eq!(format!("{}", Confidence::High), "HIGH");
185 }
186
187 #[test]
188 fn confidence_display_low() {
189 assert_eq!(format!("{}", Confidence::Low), "LOW");
190 }
191
192 #[test]
193 fn confidence_equality() {
194 assert_eq!(Confidence::High, Confidence::High);
195 assert_eq!(Confidence::Low, Confidence::Low);
196 assert_ne!(Confidence::High, Confidence::Low);
197 }
198
199 #[test]
200 fn string_literal_match_creation() {
201 let m = StringLiteralMatch {
202 file: "test.ts".to_string(),
203 line: 42,
204 context: "allowlist".to_string(),
205 };
206 assert_eq!(m.file, "test.ts");
207 assert_eq!(m.line, 42);
208 assert_eq!(m.context, "allowlist");
209 }
210
211 #[test]
212 fn command_gap_creation() {
213 let gap = CommandGap {
214 name: "test_cmd".to_string(),
215 implementation_name: Some("testCmd".to_string()),
216 locations: vec![("test.ts".to_string(), 10)],
217 confidence: Some(Confidence::High),
218 string_literal_matches: vec![],
219 };
220 assert_eq!(gap.name, "test_cmd");
221 assert_eq!(gap.implementation_name, Some("testCmd".to_string()));
222 assert_eq!(gap.locations.len(), 1);
223 assert_eq!(gap.confidence, Some(Confidence::High));
224 }
225
226 #[test]
227 fn ai_insight_creation() {
228 let insight = AiInsight {
229 title: "Test Insight".to_string(),
230 severity: "warning".to_string(),
231 message: "Some message".to_string(),
232 };
233 assert_eq!(insight.title, "Test Insight");
234 assert_eq!(insight.severity, "warning");
235 }
236
237 #[test]
238 fn graph_node_creation() {
239 let node = GraphNode {
240 id: "src/main.ts".to_string(),
241 label: "main.ts".to_string(),
242 loc: 100,
243 x: 0.5,
244 y: 0.5,
245 component: 0,
246 degree: 3,
247 detached: false,
248 };
249 assert_eq!(node.id, "src/main.ts");
250 assert_eq!(node.loc, 100);
251 assert!(!node.detached);
252 }
253
254 #[test]
255 fn command_bridge_creation() {
256 let bridge = CommandBridge {
257 name: "get_user".to_string(),
258 frontend_calls: vec![("src/app.ts".to_string(), 10)],
259 backend_handler: Some(("src-tauri/src/lib.rs".to_string(), 20)),
260 has_handler: true,
261 is_called: true,
262 };
263 assert_eq!(bridge.name, "get_user");
264 assert!(bridge.has_handler);
265 assert!(bridge.backend_handler.is_some());
266 }
267}