greppy/trace/output/
mod.rs

1//! Output formatters for trace results
2//!
3//! Provides multiple output formats for trace results:
4//! - ASCII: Rich box-drawing with ANSI colors (terminal)
5//! - Plain: Simple text without colors (piping/logs)
6//! - JSON: Machine-readable format (tooling integration)
7//! - CSV: Spreadsheet-compatible format
8//! - DOT: Graph visualization format
9//! - Markdown: Documentation format
10//!
11//! @module trace/output
12
13pub mod ascii;
14pub mod json;
15pub mod plain;
16
17// =============================================================================
18// TYPES
19// =============================================================================
20
21/// Output format selection
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub enum OutputFormat {
24    /// Rich ASCII with colors and box-drawing
25    #[default]
26    Ascii,
27    /// Plain text without ANSI codes
28    Plain,
29    /// JSON for machine consumption
30    Json,
31    /// CSV for spreadsheets
32    Csv,
33    /// DOT for graph visualization
34    Dot,
35    /// Markdown for documentation
36    Markdown,
37}
38
39/// A single step in an invocation chain
40#[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/// A complete invocation path from entry point to target
52#[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/// Reference to a symbol with context
60#[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/// Kind of reference
72#[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/// Symbol information for dead code analysis
97#[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    /// Cross-reference: potential callers that could use this symbol
105    #[serde(skip_serializing_if = "Vec::is_empty")]
106    pub potential_callers: Vec<PotentialCaller>,
107}
108
109/// A potential caller/reference for dead code cross-referencing
110#[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/// Data flow step
119#[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/// Flow action type
129#[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/// Impact analysis result
154#[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/// Risk level for impact analysis
170#[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/// Pattern match result
191#[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/// Variable in scope
203#[derive(Debug, Clone, serde::Serialize)]
204pub struct ScopeVariable {
205    pub name: String,
206    pub kind: String,
207    pub defined_at: u32,
208}
209
210// =============================================================================
211// TRACE RESULT TYPES
212// =============================================================================
213
214/// Result of a symbol trace operation
215#[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/// Result of a reference trace operation
227#[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/// Result of dead code analysis
241#[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/// Result of data flow analysis
250#[derive(Debug, Clone, serde::Serialize)]
251pub struct FlowResult {
252    pub symbol: String,
253    pub flow_paths: Vec<Vec<FlowStep>>,
254}
255
256/// Result of module tracing
257#[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/// Result of pattern search
268#[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/// Result of scope analysis
277#[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/// Result of statistics computation
289#[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
305// =============================================================================
306// FORMATTER TRAIT
307// =============================================================================
308
309/// Trait for formatting trace output
310pub trait TraceFormatter {
311    /// Format invocation paths for a symbol
312    fn format_trace(&self, result: &TraceResult) -> String;
313
314    /// Format references to a symbol
315    fn format_refs(&self, result: &RefsResult) -> String;
316
317    /// Format dead code analysis results
318    fn format_dead_code(&self, result: &DeadCodeResult) -> String;
319
320    /// Format data flow analysis results
321    fn format_flow(&self, result: &FlowResult) -> String;
322
323    /// Format impact analysis results
324    fn format_impact(&self, result: &ImpactResult) -> String;
325
326    /// Format module tracing results
327    fn format_module(&self, result: &ModuleResult) -> String;
328
329    /// Format pattern search results
330    fn format_pattern(&self, result: &PatternResult) -> String;
331
332    /// Format scope analysis results
333    fn format_scope(&self, result: &ScopeResult) -> String;
334
335    /// Format statistics results
336    fn format_stats(&self, result: &StatsResult) -> String;
337}
338
339// =============================================================================
340// FACTORY FUNCTION
341// =============================================================================
342
343/// Create a formatter for the given output format
344pub 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
355// =============================================================================
356// RE-EXPORTS
357// =============================================================================
358
359pub use ascii::AsciiFormatter;
360pub use json::JsonFormatter;
361pub use plain::{CsvFormatter, DotFormatter, MarkdownFormatter, PlainFormatter};