1pub mod ascii;
14pub mod json;
15pub mod plain;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub enum OutputFormat {
24 #[default]
26 Ascii,
27 Plain,
29 Json,
31 Csv,
33 Dot,
35 Markdown,
37}
38
39#[derive(Debug, Clone, serde::Serialize)]
41pub struct ChainStep {
42 pub symbol: String,
43 pub file: String,
44 pub line: u32,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub column: Option<u16>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub context: Option<String>,
49}
50
51#[derive(Debug, Clone, serde::Serialize)]
53pub struct InvocationPath {
54 pub entry_point: String,
55 pub entry_kind: String,
56 pub chain: Vec<ChainStep>,
57}
58
59#[derive(Debug, Clone, serde::Serialize)]
61pub struct ReferenceInfo {
62 pub file: String,
63 pub line: u32,
64 pub column: u16,
65 pub kind: ReferenceKind,
66 pub context: String,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub enclosing_symbol: Option<String>,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
73#[serde(rename_all = "lowercase")]
74pub enum ReferenceKind {
75 Read,
76 Write,
77 Call,
78 TypeAnnotation,
79 Import,
80 Export,
81}
82
83impl std::fmt::Display for ReferenceKind {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 match self {
86 ReferenceKind::Read => write!(f, "read"),
87 ReferenceKind::Write => write!(f, "write"),
88 ReferenceKind::Call => write!(f, "call"),
89 ReferenceKind::TypeAnnotation => write!(f, "type"),
90 ReferenceKind::Import => write!(f, "import"),
91 ReferenceKind::Export => write!(f, "export"),
92 }
93 }
94}
95
96#[derive(Debug, Clone, serde::Serialize)]
98pub struct DeadSymbol {
99 pub name: String,
100 pub kind: String,
101 pub file: String,
102 pub line: u32,
103 pub reason: String,
104 #[serde(skip_serializing_if = "Vec::is_empty")]
106 pub potential_callers: Vec<PotentialCaller>,
107}
108
109#[derive(Debug, Clone, serde::Serialize)]
111pub struct PotentialCaller {
112 pub name: String,
113 pub file: String,
114 pub line: u32,
115 pub reason: String,
116}
117
118#[derive(Debug, Clone, serde::Serialize)]
120pub struct FlowStep {
121 pub variable: String,
122 pub action: FlowAction,
123 pub file: String,
124 pub line: u32,
125 pub expression: String,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
130#[serde(rename_all = "lowercase")]
131pub enum FlowAction {
132 Define,
133 Assign,
134 Read,
135 PassToFunction,
136 ReturnFrom,
137 Mutate,
138}
139
140impl std::fmt::Display for FlowAction {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 match self {
143 FlowAction::Define => write!(f, "define"),
144 FlowAction::Assign => write!(f, "assign"),
145 FlowAction::Read => write!(f, "read"),
146 FlowAction::PassToFunction => write!(f, "pass"),
147 FlowAction::ReturnFrom => write!(f, "return"),
148 FlowAction::Mutate => write!(f, "mutate"),
149 }
150 }
151}
152
153#[derive(Debug, Clone, serde::Serialize)]
155pub struct ImpactResult {
156 pub symbol: String,
157 pub file: String,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub defined_at: Option<String>,
160 pub direct_callers: Vec<String>,
161 pub direct_caller_count: usize,
162 pub transitive_callers: Vec<String>,
163 pub transitive_caller_count: usize,
164 pub affected_entry_points: Vec<String>,
165 pub files_affected: Vec<String>,
166 pub risk_level: RiskLevel,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
171#[serde(rename_all = "lowercase")]
172pub enum RiskLevel {
173 Low,
174 Medium,
175 High,
176 Critical,
177}
178
179impl std::fmt::Display for RiskLevel {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 match self {
182 RiskLevel::Low => write!(f, "low"),
183 RiskLevel::Medium => write!(f, "medium"),
184 RiskLevel::High => write!(f, "high"),
185 RiskLevel::Critical => write!(f, "critical"),
186 }
187 }
188}
189
190#[derive(Debug, Clone, serde::Serialize)]
192pub struct PatternMatch {
193 pub file: String,
194 pub line: u32,
195 pub column: u16,
196 pub matched_text: String,
197 pub context: String,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub enclosing_symbol: Option<String>,
200}
201
202#[derive(Debug, Clone, serde::Serialize)]
204pub struct ScopeVariable {
205 pub name: String,
206 pub kind: String,
207 pub defined_at: u32,
208}
209
210#[derive(Debug, Clone, serde::Serialize)]
216pub struct TraceResult {
217 pub symbol: String,
218 #[serde(skip_serializing_if = "Option::is_none")]
219 pub defined_at: Option<String>,
220 pub kind: String,
221 pub invocation_paths: Vec<InvocationPath>,
222 pub total_paths: usize,
223 pub entry_points: usize,
224}
225
226#[derive(Debug, Clone, serde::Serialize)]
228pub struct RefsResult {
229 pub symbol: String,
230 #[serde(skip_serializing_if = "Option::is_none")]
231 pub defined_at: Option<String>,
232 #[serde(skip_serializing_if = "Option::is_none")]
233 pub symbol_kind: Option<String>,
234 pub references: Vec<ReferenceInfo>,
235 pub total_refs: usize,
236 pub by_kind: std::collections::HashMap<String, usize>,
237 pub by_file: std::collections::HashMap<String, usize>,
238}
239
240#[derive(Debug, Clone, serde::Serialize)]
242pub struct DeadCodeResult {
243 pub symbols: Vec<DeadSymbol>,
244 pub total_dead: usize,
245 pub by_kind: std::collections::HashMap<String, usize>,
246 pub by_file: std::collections::HashMap<String, usize>,
247}
248
249#[derive(Debug, Clone, serde::Serialize)]
251pub struct FlowResult {
252 pub symbol: String,
253 pub flow_paths: Vec<Vec<FlowStep>>,
254}
255
256#[derive(Debug, Clone, serde::Serialize)]
258pub struct ModuleResult {
259 pub module: String,
260 pub file_path: String,
261 pub exports: Vec<String>,
262 pub imported_by: Vec<String>,
263 pub dependencies: Vec<String>,
264 pub circular_deps: Vec<String>,
265}
266
267#[derive(Debug, Clone, serde::Serialize)]
269pub struct PatternResult {
270 pub pattern: String,
271 pub total_matches: usize,
272 pub matches: Vec<PatternMatch>,
273 pub by_file: std::collections::HashMap<String, usize>,
274}
275
276#[derive(Debug, Clone, serde::Serialize)]
278pub struct ScopeResult {
279 pub file: String,
280 pub line: u32,
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub enclosing_scope: Option<String>,
283 pub local_variables: Vec<ScopeVariable>,
284 pub parameters: Vec<ScopeVariable>,
285 pub imports: Vec<String>,
286}
287
288#[derive(Debug, Clone, serde::Serialize)]
290pub struct StatsResult {
291 pub total_files: usize,
292 pub total_symbols: usize,
293 pub total_tokens: usize,
294 pub total_references: usize,
295 pub total_edges: usize,
296 pub total_entry_points: usize,
297 pub symbols_by_kind: std::collections::HashMap<String, usize>,
298 pub files_by_extension: std::collections::HashMap<String, usize>,
299 pub most_referenced: Vec<(String, usize)>,
300 pub largest_files: Vec<(String, usize)>,
301 pub max_call_depth: usize,
302 pub avg_call_depth: f32,
303}
304
305pub trait TraceFormatter {
311 fn format_trace(&self, result: &TraceResult) -> String;
313
314 fn format_refs(&self, result: &RefsResult) -> String;
316
317 fn format_dead_code(&self, result: &DeadCodeResult) -> String;
319
320 fn format_flow(&self, result: &FlowResult) -> String;
322
323 fn format_impact(&self, result: &ImpactResult) -> String;
325
326 fn format_module(&self, result: &ModuleResult) -> String;
328
329 fn format_pattern(&self, result: &PatternResult) -> String;
331
332 fn format_scope(&self, result: &ScopeResult) -> String;
334
335 fn format_stats(&self, result: &StatsResult) -> String;
337}
338
339pub fn create_formatter(format: OutputFormat) -> Box<dyn TraceFormatter> {
345 match format {
346 OutputFormat::Ascii => Box::new(ascii::AsciiFormatter::new()),
347 OutputFormat::Plain => Box::new(plain::PlainFormatter::new()),
348 OutputFormat::Json => Box::new(json::JsonFormatter::new()),
349 OutputFormat::Csv => Box::new(plain::CsvFormatter::new()),
350 OutputFormat::Dot => Box::new(plain::DotFormatter::new()),
351 OutputFormat::Markdown => Box::new(plain::MarkdownFormatter::new()),
352 }
353}
354
355pub use ascii::AsciiFormatter;
360pub use json::JsonFormatter;
361pub use plain::{CsvFormatter, DotFormatter, MarkdownFormatter, PlainFormatter};