fn apply_filters(
mut table: SymbolTable,
filter: Option<crate::cli::SymbolTypeFilter>,
query: Option<String>,
) -> Result<SymbolTable> {
if let Some(type_filter) = filter {
table.symbols.retain(|s| match type_filter {
crate::cli::SymbolTypeFilter::Functions => {
s.kind == SymbolKind::Function || s.kind == SymbolKind::Method
}
crate::cli::SymbolTypeFilter::Classes => s.kind == SymbolKind::Class,
crate::cli::SymbolTypeFilter::Types => {
s.kind == SymbolKind::Type
|| s.kind == SymbolKind::Interface
|| s.kind == SymbolKind::Enum
}
crate::cli::SymbolTypeFilter::Variables => {
s.kind == SymbolKind::Variable || s.kind == SymbolKind::Constant
}
crate::cli::SymbolTypeFilter::Modules => s.kind == SymbolKind::Module,
crate::cli::SymbolTypeFilter::All => true,
});
}
if let Some(q) = query {
let q_lower = q.to_lowercase();
table
.symbols
.retain(|s| s.name.to_lowercase().contains(&q_lower));
}
Ok(table)
}
pub fn format_output(
table: SymbolTable,
format: crate::cli::SymbolTableOutputFormat,
show_unreferenced: bool,
_show_references: bool,
) -> Result<String> {
match format {
crate::cli::SymbolTableOutputFormat::Json => format_json_output(&table),
crate::cli::SymbolTableOutputFormat::Human
| crate::cli::SymbolTableOutputFormat::Summary
| crate::cli::SymbolTableOutputFormat::Detailed => {
format_human_output(table, show_unreferenced)
}
crate::cli::SymbolTableOutputFormat::Csv => format_csv_output(table),
}
}
fn format_json_output(table: &SymbolTable) -> Result<String> {
Ok(serde_json::to_string_pretty(table)?)
}
fn format_human_output(table: SymbolTable, show_unreferenced: bool) -> Result<String> {
let mut output = String::new();
write_header(&mut output, table.total_symbols)?;
write_symbols_by_type(&mut output, &table.symbols)?;
if show_unreferenced {
write_unreferenced_symbols(&mut output, &table.unreferenced_symbols)?;
}
write_most_referenced(&mut output, &table.most_referenced)?;
write_top_files_by_count(&mut output, &table.symbols)?;
Ok(output)
}
fn write_header(output: &mut String, total_symbols: usize) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
writeln!(
output,
"{}{}Symbol Table Analysis{}\n",
c::BOLD, c::UNDERLINE, c::RESET
)?;
writeln!(
output,
" {}Total symbols:{} {}{}{}",
c::BOLD, c::RESET, c::BOLD_WHITE, total_symbols, c::RESET
)?;
writeln!(output, "\n{}Symbols by Type{}\n", c::BOLD, c::RESET)?;
Ok(())
}
fn write_symbols_by_type(output: &mut String, symbols: &[Symbol]) -> Result<()> {
let by_type = group_symbols_by_type(symbols);
for (kind, syms) in by_type {
write_symbol_group(output, &kind, &syms)?;
}
Ok(())
}
fn group_symbols_by_type(symbols: &[Symbol]) -> HashMap<SymbolKind, Vec<&Symbol>> {
let mut by_type: HashMap<SymbolKind, Vec<&Symbol>> = HashMap::new();
for symbol in symbols {
by_type.entry(symbol.kind.clone()).or_default().push(symbol);
}
by_type
}
fn write_symbol_group(output: &mut String, kind: &SymbolKind, syms: &[&Symbol]) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
writeln!(
output,
"{}{:?}{} ({}{}{})",
c::BOLD, kind, c::RESET, c::BOLD_WHITE, syms.len(), c::RESET
)?;
for sym in syms.iter().take(10) {
writeln!(
output,
" - {}{}{} {}{}:{}{}",
c::BOLD_WHITE, sym.name, c::RESET, c::CYAN, sym.file, sym.line, c::RESET
)?;
}
if syms.len() > 10 {
writeln!(
output,
" {}... and {} more{}",
c::DIM, syms.len() - 10, c::RESET
)?;
}
writeln!(output)?;
Ok(())
}
fn write_unreferenced_symbols(output: &mut String, unreferenced: &[String]) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
if unreferenced.is_empty() {
return Ok(());
}
writeln!(output, "\n{}Unreferenced Symbols{}\n", c::BOLD, c::RESET)?;
for name in unreferenced {
writeln!(output, " - {}{}{}", c::YELLOW, name, c::RESET)?;
}
Ok(())
}
fn write_most_referenced(output: &mut String, most_referenced: &[(String, usize)]) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
if most_referenced.is_empty() {
return Ok(());
}
writeln!(output, "\n{}Most Referenced Symbols{}\n", c::BOLD, c::RESET)?;
for (name, count) in most_referenced {
writeln!(
output,
" - {}{}{}: {}{}{} references",
c::BOLD_WHITE, name, c::RESET, c::BOLD_WHITE, count, c::RESET
)?;
}
Ok(())
}
fn write_top_files_by_count(output: &mut String, symbols: &[Symbol]) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
if symbols.is_empty() {
return Ok(());
}
writeln!(output, "\n{}Top Files by Symbol Count{}\n", c::BOLD, c::RESET)?;
let sorted_files = get_sorted_file_counts(symbols);
for (i, (file_path, count)) in sorted_files.iter().take(10).enumerate() {
let filename = extract_filename(file_path);
writeln!(
output,
"{}{}. {}{}{} - {}{}{} symbols",
c::BOLD, i + 1, c::CYAN, filename, c::RESET,
c::BOLD_WHITE, count, c::RESET
)?;
}
Ok(())
}
fn get_sorted_file_counts(symbols: &[Symbol]) -> Vec<(&str, usize)> {
let mut file_counts: HashMap<&str, usize> = HashMap::new();
for symbol in symbols {
*file_counts.entry(&symbol.file).or_insert(0) += 1;
}
let mut sorted_files: Vec<_> = file_counts.into_iter().collect();
sorted_files.sort_by(|a, b| b.1.cmp(&a.1));
sorted_files
}
fn extract_filename(file_path: &str) -> &str {
Path::new(file_path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(file_path)
}
fn format_csv_output(table: SymbolTable) -> Result<String> {
use std::fmt::Write;
let mut output = String::new();
writeln!(&mut output, "name,kind,file,line,column,visibility")?;
for sym in table.symbols {
writeln!(
&mut output,
"{},{:?},{},{},{},{:?}",
sym.name, sym.kind, sym.file, sym.line, sym.column, sym.visibility
)?;
}
Ok(output)
}